1#![allow(clippy::reversed_empty_ranges)]
80
81use crate::{
82 binding_model::{self, buffer_binding_type_alignment},
83 command::{
84 BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, MapPassErr,
85 PassErrorScope, RenderCommand, RenderCommandError, StateChange,
86 },
87 conv,
88 device::{
89 AttachmentData, Device, DeviceError, MissingDownlevelFlags,
90 RenderPassCompatibilityCheckType, RenderPassContext, SHADER_STAGE_COUNT,
91 },
92 error::{ErrorFormatter, PrettyError},
93 hal_api::HalApi,
94 hub::{Hub, Token},
95 id,
96 identity::GlobalIdentityHandlerFactory,
97 init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction},
98 pipeline::{self, PipelineFlags},
99 resource::{self, Resource},
100 storage::Storage,
101 track::RenderBundleScope,
102 validation::check_buffer_usage,
103 Label, LabelHelpers, LifeGuard, Stored,
104};
105use arrayvec::ArrayVec;
106use std::{borrow::Cow, mem, num::NonZeroU32, ops::Range};
107use thiserror::Error;
108
109use hal::CommandEncoder as _;
110
111#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
113#[cfg_attr(feature = "trace", derive(serde::Serialize))]
114#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
115pub struct RenderBundleEncoderDescriptor<'a> {
116 pub label: Label<'a>,
120 pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>,
126 pub depth_stencil: Option<wgt::RenderBundleDepthStencil>,
132 pub sample_count: u32,
136 pub multiview: Option<NonZeroU32>,
139}
140
141#[derive(Debug)]
142#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))]
143pub struct RenderBundleEncoder {
144 base: BasePass<RenderCommand>,
145 parent_id: id::DeviceId,
146 pub(crate) context: RenderPassContext,
147 pub(crate) is_depth_read_only: bool,
148 pub(crate) is_stencil_read_only: bool,
149
150 #[cfg_attr(feature = "serial-pass", serde(skip))]
152 current_bind_groups: BindGroupStateChange,
153 #[cfg_attr(feature = "serial-pass", serde(skip))]
154 current_pipeline: StateChange<id::RenderPipelineId>,
155}
156
157impl RenderBundleEncoder {
158 pub fn new(
159 desc: &RenderBundleEncoderDescriptor,
160 parent_id: id::DeviceId,
161 base: Option<BasePass<RenderCommand>>,
162 ) -> Result<Self, CreateRenderBundleError> {
163 let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
164 Some(ds) => {
165 let aspects = hal::FormatAspects::from(ds.format);
166 (
167 !aspects.contains(hal::FormatAspects::DEPTH) || ds.depth_read_only,
168 !aspects.contains(hal::FormatAspects::STENCIL) || ds.stencil_read_only,
169 )
170 }
171 None => (true, true),
175 };
176
177 Ok(Self {
180 base: base.unwrap_or_else(|| BasePass::new(&desc.label)),
181 parent_id,
182 context: RenderPassContext {
183 attachments: AttachmentData {
184 colors: if desc.color_formats.len() > hal::MAX_COLOR_ATTACHMENTS {
185 return Err(CreateRenderBundleError::ColorAttachment(
186 ColorAttachmentError::TooMany {
187 given: desc.color_formats.len(),
188 limit: hal::MAX_COLOR_ATTACHMENTS,
189 },
190 ));
191 } else {
192 desc.color_formats.iter().cloned().collect()
193 },
194 resolves: ArrayVec::new(),
195 depth_stencil: desc.depth_stencil.map(|ds| ds.format),
196 },
197 sample_count: {
198 let sc = desc.sample_count;
199 if sc == 0 || sc > 32 || !conv::is_power_of_two_u32(sc) {
200 return Err(CreateRenderBundleError::InvalidSampleCount(sc));
201 }
202 sc
203 },
204 multiview: desc.multiview,
205 },
206
207 is_depth_read_only,
208 is_stencil_read_only,
209 current_bind_groups: BindGroupStateChange::new(),
210 current_pipeline: StateChange::new(),
211 })
212 }
213
214 pub fn dummy(parent_id: id::DeviceId) -> Self {
215 Self {
216 base: BasePass::new(&None),
217 parent_id,
218 context: RenderPassContext {
219 attachments: AttachmentData {
220 colors: ArrayVec::new(),
221 resolves: ArrayVec::new(),
222 depth_stencil: None,
223 },
224 sample_count: 0,
225 multiview: None,
226 },
227 is_depth_read_only: false,
228 is_stencil_read_only: false,
229
230 current_bind_groups: BindGroupStateChange::new(),
231 current_pipeline: StateChange::new(),
232 }
233 }
234
235 #[cfg(feature = "trace")]
236 pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand> {
237 BasePass::from_ref(self.base.as_ref())
238 }
239
240 pub fn parent(&self) -> id::DeviceId {
241 self.parent_id
242 }
243
244 pub(crate) fn finish<A: HalApi, G: GlobalIdentityHandlerFactory>(
255 self,
256 desc: &RenderBundleDescriptor,
257 device: &Device<A>,
258 hub: &Hub<A, G>,
259 token: &mut Token<Device<A>>,
260 ) -> Result<RenderBundle<A>, RenderBundleError> {
261 let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(token);
262 let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
263 let (pipeline_guard, mut token) = hub.render_pipelines.read(&mut token);
264 let (query_set_guard, mut token) = hub.query_sets.read(&mut token);
265 let (buffer_guard, mut token) = hub.buffers.read(&mut token);
266 let (texture_guard, _) = hub.textures.read(&mut token);
267
268 let mut state = State {
269 trackers: RenderBundleScope::new(
270 &*buffer_guard,
271 &*texture_guard,
272 &*bind_group_guard,
273 &*pipeline_guard,
274 &*query_set_guard,
275 ),
276 pipeline: None,
277 bind: (0..hal::MAX_BIND_GROUPS).map(|_| None).collect(),
278 vertex: (0..hal::MAX_VERTEX_BUFFERS).map(|_| None).collect(),
279 index: None,
280 flat_dynamic_offsets: Vec::new(),
281 };
282 let mut commands = Vec::new();
283 let mut buffer_memory_init_actions = Vec::new();
284 let mut texture_memory_init_actions = Vec::new();
285
286 let base = self.base.as_ref();
287 let mut next_dynamic_offset = 0;
288
289 for &command in base.commands {
290 match command {
291 RenderCommand::SetBindGroup {
292 index,
293 num_dynamic_offsets,
294 bind_group_id,
295 } => {
296 let scope = PassErrorScope::SetBindGroup(bind_group_id);
297
298 let bind_group: &binding_model::BindGroup<A> = state
299 .trackers
300 .bind_groups
301 .add_single(&*bind_group_guard, bind_group_id)
302 .ok_or(RenderCommandError::InvalidBindGroup(bind_group_id))
303 .map_pass_err(scope)?;
304 self.check_valid_to_use(bind_group.device_id.value)
305 .map_pass_err(scope)?;
306
307 let max_bind_groups = device.limits.max_bind_groups;
308 if index >= max_bind_groups {
309 return Err(RenderCommandError::BindGroupIndexOutOfRange {
310 index,
311 max: max_bind_groups,
312 })
313 .map_pass_err(scope);
314 }
315
316 let num_dynamic_offsets = num_dynamic_offsets as usize;
318 let offsets_range =
319 next_dynamic_offset..next_dynamic_offset + num_dynamic_offsets;
320 next_dynamic_offset = offsets_range.end;
321 let offsets = &base.dynamic_offsets[offsets_range.clone()];
322
323 if bind_group.dynamic_binding_info.len() != offsets.len() {
324 return Err(RenderCommandError::InvalidDynamicOffsetCount {
325 actual: offsets.len(),
326 expected: bind_group.dynamic_binding_info.len(),
327 })
328 .map_pass_err(scope);
329 }
330
331 for (offset, info) in offsets
333 .iter()
334 .map(|offset| *offset as wgt::BufferAddress)
335 .zip(bind_group.dynamic_binding_info.iter())
336 {
337 let (alignment, limit_name) =
338 buffer_binding_type_alignment(&device.limits, info.binding_type);
339 if offset % alignment as u64 != 0 {
340 return Err(RenderCommandError::UnalignedBufferOffset(
341 offset, limit_name, alignment,
342 ))
343 .map_pass_err(scope);
344 }
345 }
346
347 buffer_memory_init_actions.extend_from_slice(&bind_group.used_buffer_ranges);
348 texture_memory_init_actions.extend_from_slice(&bind_group.used_texture_ranges);
349
350 state.set_bind_group(index, bind_group_id, bind_group.layout_id, offsets_range);
351 unsafe {
352 state
353 .trackers
354 .merge_bind_group(&*texture_guard, &bind_group.used)
355 .map_pass_err(scope)?
356 };
357 }
360 RenderCommand::SetPipeline(pipeline_id) => {
361 let scope = PassErrorScope::SetPipelineRender(pipeline_id);
362
363 let pipeline: &pipeline::RenderPipeline<A> = state
364 .trackers
365 .render_pipelines
366 .add_single(&*pipeline_guard, pipeline_id)
367 .ok_or(RenderCommandError::InvalidPipeline(pipeline_id))
368 .map_pass_err(scope)?;
369 self.check_valid_to_use(pipeline.device_id.value)
370 .map_pass_err(scope)?;
371
372 self.context
373 .check_compatible(&pipeline.pass_context, RenderPassCompatibilityCheckType::RenderPipeline)
374 .map_err(RenderCommandError::IncompatiblePipelineTargets)
375 .map_pass_err(scope)?;
376
377 if (pipeline.flags.contains(PipelineFlags::WRITES_DEPTH)
378 && self.is_depth_read_only)
379 || (pipeline.flags.contains(PipelineFlags::WRITES_STENCIL)
380 && self.is_stencil_read_only)
381 {
382 return Err(RenderCommandError::IncompatiblePipelineRods)
383 .map_pass_err(scope);
384 }
385
386 let layout = &pipeline_layout_guard[pipeline.layout_id.value];
387 let pipeline_state = PipelineState::new(pipeline_id, pipeline, layout);
388
389 commands.push(command);
390
391 if let Some(iter) = pipeline_state.zero_push_constants() {
393 commands.extend(iter)
394 }
395
396 state.invalidate_bind_groups(&pipeline_state, layout);
397 state.pipeline = Some(pipeline_state);
398 }
399 RenderCommand::SetIndexBuffer {
400 buffer_id,
401 index_format,
402 offset,
403 size,
404 } => {
405 let scope = PassErrorScope::SetIndexBuffer(buffer_id);
406 let buffer: &resource::Buffer<A> = state
407 .trackers
408 .buffers
409 .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDEX)
410 .map_pass_err(scope)?;
411 self.check_valid_to_use(buffer.device_id.value)
412 .map_pass_err(scope)?;
413 check_buffer_usage(buffer.usage, wgt::BufferUsages::INDEX)
414 .map_pass_err(scope)?;
415
416 let end = match size {
417 Some(s) => offset + s.get(),
418 None => buffer.size,
419 };
420 buffer_memory_init_actions.extend(buffer.initialization_status.create_action(
421 buffer_id,
422 offset..end,
423 MemoryInitKind::NeedsInitializedMemory,
424 ));
425 state.set_index_buffer(buffer_id, index_format, offset..end);
426 }
427 RenderCommand::SetVertexBuffer {
428 slot,
429 buffer_id,
430 offset,
431 size,
432 } => {
433 let scope = PassErrorScope::SetVertexBuffer(buffer_id);
434 let buffer: &resource::Buffer<A> = state
435 .trackers
436 .buffers
437 .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::VERTEX)
438 .map_pass_err(scope)?;
439 self.check_valid_to_use(buffer.device_id.value)
440 .map_pass_err(scope)?;
441 check_buffer_usage(buffer.usage, wgt::BufferUsages::VERTEX)
442 .map_pass_err(scope)?;
443
444 let end = match size {
445 Some(s) => offset + s.get(),
446 None => buffer.size,
447 };
448 buffer_memory_init_actions.extend(buffer.initialization_status.create_action(
449 buffer_id,
450 offset..end,
451 MemoryInitKind::NeedsInitializedMemory,
452 ));
453 state.vertex[slot as usize] = Some(VertexState::new(buffer_id, offset..end));
454 }
455 RenderCommand::SetPushConstant {
456 stages,
457 offset,
458 size_bytes,
459 values_offset: _,
460 } => {
461 let scope = PassErrorScope::SetPushConstant;
462 let end_offset = offset + size_bytes;
463
464 let pipeline = state.pipeline(scope)?;
465 let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id];
466
467 pipeline_layout
468 .validate_push_constant_ranges(stages, offset, end_offset)
469 .map_pass_err(scope)?;
470
471 commands.push(command);
472 }
473 RenderCommand::Draw {
474 vertex_count,
475 instance_count,
476 first_vertex,
477 first_instance,
478 } => {
479 let scope = PassErrorScope::Draw {
480 indexed: false,
481 indirect: false,
482 pipeline: state.pipeline_id(),
483 };
484 let pipeline = state.pipeline(scope)?;
485 let used_bind_groups = pipeline.used_bind_groups;
486 let vertex_limits = state.vertex_limits(pipeline);
487 let last_vertex = first_vertex + vertex_count;
488 if last_vertex > vertex_limits.vertex_limit {
489 return Err(DrawError::VertexBeyondLimit {
490 last_vertex,
491 vertex_limit: vertex_limits.vertex_limit,
492 slot: vertex_limits.vertex_limit_slot,
493 })
494 .map_pass_err(scope);
495 }
496 let last_instance = first_instance + instance_count;
497 if last_instance > vertex_limits.instance_limit {
498 return Err(DrawError::InstanceBeyondLimit {
499 last_instance,
500 instance_limit: vertex_limits.instance_limit,
501 slot: vertex_limits.instance_limit_slot,
502 })
503 .map_pass_err(scope);
504 }
505 commands.extend(state.flush_vertices());
506 commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets));
507 commands.push(command);
508 }
509 RenderCommand::DrawIndexed {
510 index_count,
511 instance_count,
512 first_index,
513 base_vertex: _,
514 first_instance,
515 } => {
516 let scope = PassErrorScope::Draw {
517 indexed: true,
518 indirect: false,
519 pipeline: state.pipeline_id(),
520 };
521 let pipeline = state.pipeline(scope)?;
522 let used_bind_groups = pipeline.used_bind_groups;
523 let index = match state.index {
524 Some(ref index) => index,
525 None => return Err(DrawError::MissingIndexBuffer).map_pass_err(scope),
526 };
527 let vertex_limits = state.vertex_limits(pipeline);
529 let index_limit = index.limit();
530 let last_index = first_index + index_count;
531 if last_index > index_limit {
532 return Err(DrawError::IndexBeyondLimit {
533 last_index,
534 index_limit,
535 })
536 .map_pass_err(scope);
537 }
538 let last_instance = first_instance + instance_count;
539 if last_instance > vertex_limits.instance_limit {
540 return Err(DrawError::InstanceBeyondLimit {
541 last_instance,
542 instance_limit: vertex_limits.instance_limit,
543 slot: vertex_limits.instance_limit_slot,
544 })
545 .map_pass_err(scope);
546 }
547 commands.extend(state.flush_index());
548 commands.extend(state.flush_vertices());
549 commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets));
550 commands.push(command);
551 }
552 RenderCommand::MultiDrawIndirect {
553 buffer_id,
554 offset,
555 count: None,
556 indexed: false,
557 } => {
558 let scope = PassErrorScope::Draw {
559 indexed: false,
560 indirect: true,
561 pipeline: state.pipeline_id(),
562 };
563 device
564 .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)
565 .map_pass_err(scope)?;
566
567 let pipeline = state.pipeline(scope)?;
568 let used_bind_groups = pipeline.used_bind_groups;
569
570 let buffer: &resource::Buffer<A> = state
571 .trackers
572 .buffers
573 .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT)
574 .map_pass_err(scope)?;
575 self.check_valid_to_use(buffer.device_id.value)
576 .map_pass_err(scope)?;
577 check_buffer_usage(buffer.usage, wgt::BufferUsages::INDIRECT)
578 .map_pass_err(scope)?;
579
580 buffer_memory_init_actions.extend(buffer.initialization_status.create_action(
581 buffer_id,
582 offset..(offset + mem::size_of::<wgt::DrawIndirectArgs>() as u64),
583 MemoryInitKind::NeedsInitializedMemory,
584 ));
585
586 commands.extend(state.flush_vertices());
587 commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets));
588 commands.push(command);
589 }
590 RenderCommand::MultiDrawIndirect {
591 buffer_id,
592 offset,
593 count: None,
594 indexed: true,
595 } => {
596 let scope = PassErrorScope::Draw {
597 indexed: true,
598 indirect: true,
599 pipeline: state.pipeline_id(),
600 };
601 device
602 .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)
603 .map_pass_err(scope)?;
604
605 let pipeline = state.pipeline(scope)?;
606 let used_bind_groups = pipeline.used_bind_groups;
607
608 let buffer: &resource::Buffer<A> = state
609 .trackers
610 .buffers
611 .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT)
612 .map_pass_err(scope)?;
613 self.check_valid_to_use(buffer.device_id.value)
614 .map_pass_err(scope)?;
615 check_buffer_usage(buffer.usage, wgt::BufferUsages::INDIRECT)
616 .map_pass_err(scope)?;
617
618 buffer_memory_init_actions.extend(buffer.initialization_status.create_action(
619 buffer_id,
620 offset..(offset + mem::size_of::<wgt::DrawIndirectArgs>() as u64),
621 MemoryInitKind::NeedsInitializedMemory,
622 ));
623
624 let index = match state.index {
625 Some(ref mut index) => index,
626 None => return Err(DrawError::MissingIndexBuffer).map_pass_err(scope),
627 };
628
629 commands.extend(index.flush());
630 commands.extend(state.flush_vertices());
631 commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets));
632 commands.push(command);
633 }
634 RenderCommand::MultiDrawIndirect { .. }
635 | RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
636 RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
637 RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
638 RenderCommand::PopDebugGroup => unimplemented!(),
639 RenderCommand::WriteTimestamp { .. } | RenderCommand::BeginOcclusionQuery { .. }
641 | RenderCommand::EndOcclusionQuery
642 | RenderCommand::BeginPipelineStatisticsQuery { .. }
643 | RenderCommand::EndPipelineStatisticsQuery => unimplemented!(),
644 RenderCommand::ExecuteBundle(_)
645 | RenderCommand::SetBlendConstant(_)
646 | RenderCommand::SetStencilReference(_)
647 | RenderCommand::SetViewport { .. }
648 | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
649 }
650 }
651
652 Ok(RenderBundle {
653 base: BasePass {
654 label: desc.label.as_ref().map(|cow| cow.to_string()),
655 commands,
656 dynamic_offsets: state.flat_dynamic_offsets,
657 string_data: Vec::new(),
658 push_constant_data: Vec::new(),
659 },
660 is_depth_read_only: self.is_depth_read_only,
661 is_stencil_read_only: self.is_stencil_read_only,
662 device_id: Stored {
663 value: id::Valid(self.parent_id),
664 ref_count: device.life_guard.add_ref(),
665 },
666 used: state.trackers,
667 buffer_memory_init_actions,
668 texture_memory_init_actions,
669 context: self.context,
670 life_guard: LifeGuard::new(desc.label.borrow_or_default()),
671 discard_hal_labels: device
672 .instance_flags
673 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS),
674 })
675 }
676
677 fn check_valid_to_use(
678 &self,
679 device_id: id::Valid<id::DeviceId>,
680 ) -> Result<(), RenderBundleErrorInner> {
681 if device_id.0 != self.parent_id {
682 return Err(RenderBundleErrorInner::NotValidToUse);
683 }
684
685 Ok(())
686 }
687
688 pub fn set_index_buffer(
689 &mut self,
690 buffer_id: id::BufferId,
691 index_format: wgt::IndexFormat,
692 offset: wgt::BufferAddress,
693 size: Option<wgt::BufferSize>,
694 ) {
695 self.base.commands.push(RenderCommand::SetIndexBuffer {
696 buffer_id,
697 index_format,
698 offset,
699 size,
700 });
701 }
702}
703
704#[derive(Clone, Debug, Error)]
706#[non_exhaustive]
707pub enum CreateRenderBundleError {
708 #[error(transparent)]
709 ColorAttachment(#[from] ColorAttachmentError),
710 #[error("Invalid number of samples {0}")]
711 InvalidSampleCount(u32),
712}
713
714#[derive(Clone, Debug, Error)]
716#[non_exhaustive]
717pub enum ExecutionError {
718 #[error("Buffer {0:?} is destroyed")]
719 DestroyedBuffer(id::BufferId),
720 #[error("Using {0} in a render bundle is not implemented")]
721 Unimplemented(&'static str),
722}
723impl PrettyError for ExecutionError {
724 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
725 fmt.error(self);
726 match *self {
727 Self::DestroyedBuffer(id) => {
728 fmt.buffer_label(&id);
729 }
730 Self::Unimplemented(_reason) => {}
731 };
732 }
733}
734
735pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
736
737pub struct RenderBundle<A: HalApi> {
741 base: BasePass<RenderCommand>,
744 pub(super) is_depth_read_only: bool,
745 pub(super) is_stencil_read_only: bool,
746 pub(crate) device_id: Stored<id::DeviceId>,
747 pub(crate) used: RenderBundleScope<A>,
748 pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
749 pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
750 pub(super) context: RenderPassContext,
751 pub(crate) life_guard: LifeGuard,
752 discard_hal_labels: bool,
753}
754
755#[cfg(any(
756 not(target_arch = "wasm32"),
757 all(
758 feature = "fragile-send-sync-non-atomic-wasm",
759 not(target_feature = "atomics")
760 )
761))]
762unsafe impl<A: HalApi> Send for RenderBundle<A> {}
763#[cfg(any(
764 not(target_arch = "wasm32"),
765 all(
766 feature = "fragile-send-sync-non-atomic-wasm",
767 not(target_feature = "atomics")
768 )
769))]
770unsafe impl<A: HalApi> Sync for RenderBundle<A> {}
771
772impl<A: HalApi> RenderBundle<A> {
773 pub(super) unsafe fn execute(
783 &self,
784 raw: &mut A::CommandEncoder,
785 pipeline_layout_guard: &Storage<
786 crate::binding_model::PipelineLayout<A>,
787 id::PipelineLayoutId,
788 >,
789 bind_group_guard: &Storage<crate::binding_model::BindGroup<A>, id::BindGroupId>,
790 pipeline_guard: &Storage<crate::pipeline::RenderPipeline<A>, id::RenderPipelineId>,
791 buffer_guard: &Storage<crate::resource::Buffer<A>, id::BufferId>,
792 ) -> Result<(), ExecutionError> {
793 let mut offsets = self.base.dynamic_offsets.as_slice();
794 let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
795 if !self.discard_hal_labels {
796 if let Some(ref label) = self.base.label {
797 unsafe { raw.begin_debug_marker(label) };
798 }
799 }
800
801 for command in self.base.commands.iter() {
802 match *command {
803 RenderCommand::SetBindGroup {
804 index,
805 num_dynamic_offsets,
806 bind_group_id,
807 } => {
808 let bind_group = bind_group_guard.get(bind_group_id).unwrap();
809 unsafe {
810 raw.set_bind_group(
811 &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw,
812 index,
813 &bind_group.raw,
814 &offsets[..num_dynamic_offsets as usize],
815 )
816 };
817 offsets = &offsets[num_dynamic_offsets as usize..];
818 }
819 RenderCommand::SetPipeline(pipeline_id) => {
820 let pipeline = pipeline_guard.get(pipeline_id).unwrap();
821 unsafe { raw.set_render_pipeline(&pipeline.raw) };
822
823 pipeline_layout_id = Some(pipeline.layout_id.value);
824 }
825 RenderCommand::SetIndexBuffer {
826 buffer_id,
827 index_format,
828 offset,
829 size,
830 } => {
831 let buffer = buffer_guard
832 .get(buffer_id)
833 .unwrap()
834 .raw
835 .as_ref()
836 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
837 let bb = hal::BufferBinding {
838 buffer,
839 offset,
840 size,
841 };
842 unsafe { raw.set_index_buffer(bb, index_format) };
843 }
844 RenderCommand::SetVertexBuffer {
845 slot,
846 buffer_id,
847 offset,
848 size,
849 } => {
850 let buffer = buffer_guard
851 .get(buffer_id)
852 .unwrap()
853 .raw
854 .as_ref()
855 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
856 let bb = hal::BufferBinding {
857 buffer,
858 offset,
859 size,
860 };
861 unsafe { raw.set_vertex_buffer(slot, bb) };
862 }
863 RenderCommand::SetPushConstant {
864 stages,
865 offset,
866 size_bytes,
867 values_offset,
868 } => {
869 let pipeline_layout_id = pipeline_layout_id.unwrap();
870 let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
871
872 if let Some(values_offset) = values_offset {
873 let values_end_offset =
874 (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
875 let data_slice = &self.base.push_constant_data
876 [(values_offset as usize)..values_end_offset];
877
878 unsafe {
879 raw.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice)
880 }
881 } else {
882 super::push_constant_clear(
883 offset,
884 size_bytes,
885 |clear_offset, clear_data| {
886 unsafe {
887 raw.set_push_constants(
888 &pipeline_layout.raw,
889 stages,
890 clear_offset,
891 clear_data,
892 )
893 };
894 },
895 );
896 }
897 }
898 RenderCommand::Draw {
899 vertex_count,
900 instance_count,
901 first_vertex,
902 first_instance,
903 } => {
904 unsafe { raw.draw(first_vertex, vertex_count, first_instance, instance_count) };
905 }
906 RenderCommand::DrawIndexed {
907 index_count,
908 instance_count,
909 first_index,
910 base_vertex,
911 first_instance,
912 } => {
913 unsafe {
914 raw.draw_indexed(
915 first_index,
916 index_count,
917 base_vertex,
918 first_instance,
919 instance_count,
920 )
921 };
922 }
923 RenderCommand::MultiDrawIndirect {
924 buffer_id,
925 offset,
926 count: None,
927 indexed: false,
928 } => {
929 let buffer = buffer_guard
930 .get(buffer_id)
931 .unwrap()
932 .raw
933 .as_ref()
934 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
935 unsafe { raw.draw_indirect(buffer, offset, 1) };
936 }
937 RenderCommand::MultiDrawIndirect {
938 buffer_id,
939 offset,
940 count: None,
941 indexed: true,
942 } => {
943 let buffer = buffer_guard
944 .get(buffer_id)
945 .unwrap()
946 .raw
947 .as_ref()
948 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
949 unsafe { raw.draw_indexed_indirect(buffer, offset, 1) };
950 }
951 RenderCommand::MultiDrawIndirect { .. }
952 | RenderCommand::MultiDrawIndirectCount { .. } => {
953 return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
954 }
955 RenderCommand::PushDebugGroup { .. }
956 | RenderCommand::InsertDebugMarker { .. }
957 | RenderCommand::PopDebugGroup => {
958 return Err(ExecutionError::Unimplemented("debug-markers"))
959 }
960 RenderCommand::WriteTimestamp { .. }
961 | RenderCommand::BeginOcclusionQuery { .. }
962 | RenderCommand::EndOcclusionQuery
963 | RenderCommand::BeginPipelineStatisticsQuery { .. }
964 | RenderCommand::EndPipelineStatisticsQuery => {
965 return Err(ExecutionError::Unimplemented("queries"))
966 }
967 RenderCommand::ExecuteBundle(_)
968 | RenderCommand::SetBlendConstant(_)
969 | RenderCommand::SetStencilReference(_)
970 | RenderCommand::SetViewport { .. }
971 | RenderCommand::SetScissor(_) => unreachable!(),
972 }
973 }
974
975 if !self.discard_hal_labels {
976 if let Some(_) = self.base.label {
977 unsafe { raw.end_debug_marker() };
978 }
979 }
980
981 Ok(())
982 }
983}
984
985impl<A: HalApi> Resource for RenderBundle<A> {
986 const TYPE: &'static str = "RenderBundle";
987
988 fn life_guard(&self) -> &LifeGuard {
989 &self.life_guard
990 }
991}
992
993#[derive(Debug)]
999struct IndexState {
1000 buffer: id::BufferId,
1001 format: wgt::IndexFormat,
1002 range: Range<wgt::BufferAddress>,
1003 is_dirty: bool,
1004}
1005
1006impl IndexState {
1007 fn limit(&self) -> u32 {
1011 let bytes_per_index = match self.format {
1012 wgt::IndexFormat::Uint16 => 2,
1013 wgt::IndexFormat::Uint32 => 4,
1014 };
1015 ((self.range.end - self.range.start) / bytes_per_index) as u32
1016 }
1017
1018 fn flush(&mut self) -> Option<RenderCommand> {
1021 if self.is_dirty {
1022 self.is_dirty = false;
1023 Some(RenderCommand::SetIndexBuffer {
1024 buffer_id: self.buffer,
1025 index_format: self.format,
1026 offset: self.range.start,
1027 size: wgt::BufferSize::new(self.range.end - self.range.start),
1028 })
1029 } else {
1030 None
1031 }
1032 }
1033}
1034
1035#[derive(Debug)]
1045struct VertexState {
1046 buffer: id::BufferId,
1047 range: Range<wgt::BufferAddress>,
1048 is_dirty: bool,
1049}
1050
1051impl VertexState {
1052 fn new(buffer: id::BufferId, range: Range<wgt::BufferAddress>) -> Self {
1053 Self {
1054 buffer,
1055 range,
1056 is_dirty: true,
1057 }
1058 }
1059
1060 fn flush(&mut self, slot: u32) -> Option<RenderCommand> {
1064 if self.is_dirty {
1065 self.is_dirty = false;
1066 Some(RenderCommand::SetVertexBuffer {
1067 slot,
1068 buffer_id: self.buffer,
1069 offset: self.range.start,
1070 size: wgt::BufferSize::new(self.range.end - self.range.start),
1071 })
1072 } else {
1073 None
1074 }
1075 }
1076}
1077
1078#[derive(Debug)]
1080struct BindState {
1081 bind_group_id: id::BindGroupId,
1083
1084 layout_id: id::Valid<id::BindGroupLayoutId>,
1086
1087 dynamic_offsets: Range<usize>,
1090
1091 is_dirty: bool,
1094}
1095
1096#[derive(Debug)]
1097struct VertexLimitState {
1098 vertex_limit: u32,
1100 vertex_limit_slot: u32,
1102 instance_limit: u32,
1104 instance_limit_slot: u32,
1106}
1107
1108struct PipelineState {
1110 id: id::RenderPipelineId,
1112
1113 layout_id: id::Valid<id::PipelineLayoutId>,
1115
1116 steps: Vec<pipeline::VertexStep>,
1119
1120 push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
1123
1124 used_bind_groups: usize,
1126}
1127
1128impl PipelineState {
1129 fn new<A: HalApi>(
1130 pipeline_id: id::RenderPipelineId,
1131 pipeline: &pipeline::RenderPipeline<A>,
1132 layout: &binding_model::PipelineLayout<A>,
1133 ) -> Self {
1134 Self {
1135 id: pipeline_id,
1136 layout_id: pipeline.layout_id.value,
1137 steps: pipeline.vertex_steps.to_vec(),
1138 push_constant_ranges: layout.push_constant_ranges.iter().cloned().collect(),
1139 used_bind_groups: layout.bind_group_layout_ids.len(),
1140 }
1141 }
1142
1143 fn zero_push_constants(&self) -> Option<impl Iterator<Item = RenderCommand>> {
1146 if !self.push_constant_ranges.is_empty() {
1147 let nonoverlapping_ranges =
1148 super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges);
1149
1150 Some(
1151 nonoverlapping_ranges
1152 .into_iter()
1153 .map(|range| RenderCommand::SetPushConstant {
1154 stages: range.stages,
1155 offset: range.range.start,
1156 size_bytes: range.range.end - range.range.start,
1157 values_offset: None, }),
1159 )
1160 } else {
1161 None
1162 }
1163 }
1164}
1165
1166struct State<A: HalApi> {
1177 trackers: RenderBundleScope<A>,
1179
1180 pipeline: Option<PipelineState>,
1182
1183 bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>,
1185
1186 vertex: ArrayVec<Option<VertexState>, { hal::MAX_VERTEX_BUFFERS }>,
1188
1189 index: Option<IndexState>,
1192
1193 flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1200}
1201
1202impl<A: HalApi> State<A> {
1203 fn vertex_limits(&self, pipeline: &PipelineState) -> VertexLimitState {
1204 let mut vert_state = VertexLimitState {
1205 vertex_limit: u32::MAX,
1206 vertex_limit_slot: 0,
1207 instance_limit: u32::MAX,
1208 instance_limit_slot: 0,
1209 };
1210 for (idx, (vbs, step)) in self.vertex.iter().zip(&pipeline.steps).enumerate() {
1211 if let Some(ref vbs) = *vbs {
1212 let limit = ((vbs.range.end - vbs.range.start) / step.stride) as u32;
1213 match step.mode {
1214 wgt::VertexStepMode::Vertex => {
1215 if limit < vert_state.vertex_limit {
1216 vert_state.vertex_limit = limit;
1217 vert_state.vertex_limit_slot = idx as _;
1218 }
1219 }
1220 wgt::VertexStepMode::Instance => {
1221 if limit < vert_state.instance_limit {
1222 vert_state.instance_limit = limit;
1223 vert_state.instance_limit_slot = idx as _;
1224 }
1225 }
1226 }
1227 }
1228 }
1229 vert_state
1230 }
1231
1232 fn pipeline_id(&self) -> Option<id::RenderPipelineId> {
1234 self.pipeline.as_ref().map(|p| p.id)
1235 }
1236
1237 fn pipeline(&self, scope: PassErrorScope) -> Result<&PipelineState, RenderBundleError> {
1239 self.pipeline
1240 .as_ref()
1241 .ok_or(DrawError::MissingPipeline)
1242 .map_pass_err(scope)
1243 }
1244
1245 fn invalidate_bind_group_from(&mut self, index: usize) {
1247 for contents in self.bind[index..].iter_mut().flatten() {
1248 contents.is_dirty = true;
1249 }
1250 }
1251
1252 fn set_bind_group(
1253 &mut self,
1254 slot: u32,
1255 bind_group_id: id::BindGroupId,
1256 layout_id: id::Valid<id::BindGroupLayoutId>,
1257 dynamic_offsets: Range<usize>,
1258 ) {
1259 if dynamic_offsets.is_empty() {
1263 if let Some(ref contents) = self.bind[slot as usize] {
1264 if contents.bind_group_id == bind_group_id {
1265 return;
1266 }
1267 }
1268 }
1269
1270 self.bind[slot as usize] = Some(BindState {
1272 bind_group_id,
1273 layout_id,
1274 dynamic_offsets,
1275 is_dirty: true,
1276 });
1277
1278 self.invalidate_bind_group_from(slot as usize + 1);
1281 }
1282
1283 fn invalidate_bind_groups(
1297 &mut self,
1298 new: &PipelineState,
1299 layout: &binding_model::PipelineLayout<A>,
1300 ) {
1301 match self.pipeline {
1302 None => {
1303 self.invalidate_bind_group_from(0);
1305 }
1306 Some(ref old) => {
1307 if old.id == new.id {
1308 return;
1311 }
1312
1313 if old.push_constant_ranges != new.push_constant_ranges {
1315 self.invalidate_bind_group_from(0);
1316 } else {
1317 let first_changed = self
1318 .bind
1319 .iter()
1320 .zip(&layout.bind_group_layout_ids)
1321 .position(|(entry, &layout_id)| match *entry {
1322 Some(ref contents) => contents.layout_id != layout_id,
1323 None => false,
1324 });
1325 if let Some(slot) = first_changed {
1326 self.invalidate_bind_group_from(slot);
1327 }
1328 }
1329 }
1330 }
1331 }
1332
1333 fn set_index_buffer(
1335 &mut self,
1336 buffer: id::BufferId,
1337 format: wgt::IndexFormat,
1338 range: Range<wgt::BufferAddress>,
1339 ) {
1340 match self.index {
1341 Some(ref current)
1342 if current.buffer == buffer
1343 && current.format == format
1344 && current.range == range =>
1345 {
1346 return
1347 }
1348 _ => (),
1349 }
1350
1351 self.index = Some(IndexState {
1352 buffer,
1353 format,
1354 range,
1355 is_dirty: true,
1356 });
1357 }
1358
1359 fn flush_index(&mut self) -> Option<RenderCommand> {
1362 self.index.as_mut().and_then(|index| index.flush())
1363 }
1364
1365 fn flush_vertices(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
1366 self.vertex
1367 .iter_mut()
1368 .enumerate()
1369 .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)))
1370 }
1371
1372 fn flush_binds(
1374 &mut self,
1375 used_bind_groups: usize,
1376 dynamic_offsets: &[wgt::DynamicOffset],
1377 ) -> impl Iterator<Item = RenderCommand> + '_ {
1378 for contents in self.bind[..used_bind_groups].iter().flatten() {
1380 if contents.is_dirty {
1381 self.flat_dynamic_offsets
1382 .extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]);
1383 }
1384 }
1385
1386 self.bind[..used_bind_groups]
1389 .iter_mut()
1390 .enumerate()
1391 .flat_map(|(i, entry)| {
1392 if let Some(ref mut contents) = *entry {
1393 if contents.is_dirty {
1394 contents.is_dirty = false;
1395 let offsets = &contents.dynamic_offsets;
1396 return Some(RenderCommand::SetBindGroup {
1397 index: i.try_into().unwrap(),
1398 bind_group_id: contents.bind_group_id,
1399 num_dynamic_offsets: (offsets.end - offsets.start) as u8,
1400 });
1401 }
1402 }
1403 None
1404 })
1405 }
1406}
1407
1408#[derive(Clone, Debug, Error)]
1410pub(super) enum RenderBundleErrorInner {
1411 #[error("Resource is not valid to use with this render bundle because the resource and the bundle come from different devices")]
1412 NotValidToUse,
1413 #[error(transparent)]
1414 Device(#[from] DeviceError),
1415 #[error(transparent)]
1416 RenderCommand(RenderCommandError),
1417 #[error(transparent)]
1418 Draw(#[from] DrawError),
1419 #[error(transparent)]
1420 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1421}
1422
1423impl<T> From<T> for RenderBundleErrorInner
1424where
1425 T: Into<RenderCommandError>,
1426{
1427 fn from(t: T) -> Self {
1428 Self::RenderCommand(t.into())
1429 }
1430}
1431
1432#[derive(Clone, Debug, Error)]
1434#[error("{scope}")]
1435pub struct RenderBundleError {
1436 pub scope: PassErrorScope,
1437 #[source]
1438 inner: RenderBundleErrorInner,
1439}
1440
1441impl RenderBundleError {
1442 pub(crate) const INVALID_DEVICE: Self = RenderBundleError {
1443 scope: PassErrorScope::Bundle,
1444 inner: RenderBundleErrorInner::Device(DeviceError::Invalid),
1445 };
1446}
1447impl PrettyError for RenderBundleError {
1448 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
1449 fmt.error(self);
1452 self.scope.fmt_pretty(fmt);
1453 }
1454}
1455
1456impl<T, E> MapPassErr<T, RenderBundleError> for Result<T, E>
1457where
1458 E: Into<RenderBundleErrorInner>,
1459{
1460 fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderBundleError> {
1461 self.map_err(|inner| RenderBundleError {
1462 scope,
1463 inner: inner.into(),
1464 })
1465 }
1466}
1467
1468pub mod bundle_ffi {
1469 use super::{RenderBundleEncoder, RenderCommand};
1470 use crate::{id, RawString};
1471 use std::{convert::TryInto, slice};
1472 use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1473
1474 #[no_mangle]
1479 pub unsafe extern "C" fn wgpu_render_bundle_set_bind_group(
1480 bundle: &mut RenderBundleEncoder,
1481 index: u32,
1482 bind_group_id: id::BindGroupId,
1483 offsets: *const DynamicOffset,
1484 offset_length: usize,
1485 ) {
1486 let redundant = unsafe {
1487 bundle.current_bind_groups.set_and_check_redundant(
1488 bind_group_id,
1489 index,
1490 &mut bundle.base.dynamic_offsets,
1491 offsets,
1492 offset_length,
1493 )
1494 };
1495
1496 if redundant {
1497 return;
1498 }
1499
1500 bundle.base.commands.push(RenderCommand::SetBindGroup {
1501 index,
1502 num_dynamic_offsets: offset_length.try_into().unwrap(),
1503 bind_group_id,
1504 });
1505 }
1506
1507 #[no_mangle]
1508 pub extern "C" fn wgpu_render_bundle_set_pipeline(
1509 bundle: &mut RenderBundleEncoder,
1510 pipeline_id: id::RenderPipelineId,
1511 ) {
1512 if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1513 return;
1514 }
1515
1516 bundle
1517 .base
1518 .commands
1519 .push(RenderCommand::SetPipeline(pipeline_id));
1520 }
1521
1522 #[no_mangle]
1523 pub extern "C" fn wgpu_render_bundle_set_vertex_buffer(
1524 bundle: &mut RenderBundleEncoder,
1525 slot: u32,
1526 buffer_id: id::BufferId,
1527 offset: BufferAddress,
1528 size: Option<BufferSize>,
1529 ) {
1530 bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1531 slot,
1532 buffer_id,
1533 offset,
1534 size,
1535 });
1536 }
1537
1538 #[no_mangle]
1539 pub extern "C" fn wgpu_render_bundle_set_index_buffer(
1540 encoder: &mut RenderBundleEncoder,
1541 buffer: id::BufferId,
1542 index_format: IndexFormat,
1543 offset: BufferAddress,
1544 size: Option<BufferSize>,
1545 ) {
1546 encoder.set_index_buffer(buffer, index_format, offset, size);
1547 }
1548
1549 #[no_mangle]
1554 pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants(
1555 pass: &mut RenderBundleEncoder,
1556 stages: wgt::ShaderStages,
1557 offset: u32,
1558 size_bytes: u32,
1559 data: *const u8,
1560 ) {
1561 assert_eq!(
1562 offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1563 0,
1564 "Push constant offset must be aligned to 4 bytes."
1565 );
1566 assert_eq!(
1567 size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1568 0,
1569 "Push constant size must be aligned to 4 bytes."
1570 );
1571 let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1572 let value_offset = pass.base.push_constant_data.len().try_into().expect(
1573 "Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.",
1574 );
1575
1576 pass.base.push_constant_data.extend(
1577 data_slice
1578 .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
1579 .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1580 );
1581
1582 pass.base.commands.push(RenderCommand::SetPushConstant {
1583 stages,
1584 offset,
1585 size_bytes,
1586 values_offset: Some(value_offset),
1587 });
1588 }
1589
1590 #[no_mangle]
1591 pub extern "C" fn wgpu_render_bundle_draw(
1592 bundle: &mut RenderBundleEncoder,
1593 vertex_count: u32,
1594 instance_count: u32,
1595 first_vertex: u32,
1596 first_instance: u32,
1597 ) {
1598 bundle.base.commands.push(RenderCommand::Draw {
1599 vertex_count,
1600 instance_count,
1601 first_vertex,
1602 first_instance,
1603 });
1604 }
1605
1606 #[no_mangle]
1607 pub extern "C" fn wgpu_render_bundle_draw_indexed(
1608 bundle: &mut RenderBundleEncoder,
1609 index_count: u32,
1610 instance_count: u32,
1611 first_index: u32,
1612 base_vertex: i32,
1613 first_instance: u32,
1614 ) {
1615 bundle.base.commands.push(RenderCommand::DrawIndexed {
1616 index_count,
1617 instance_count,
1618 first_index,
1619 base_vertex,
1620 first_instance,
1621 });
1622 }
1623
1624 #[no_mangle]
1625 pub extern "C" fn wgpu_render_bundle_draw_indirect(
1626 bundle: &mut RenderBundleEncoder,
1627 buffer_id: id::BufferId,
1628 offset: BufferAddress,
1629 ) {
1630 bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1631 buffer_id,
1632 offset,
1633 count: None,
1634 indexed: false,
1635 });
1636 }
1637
1638 #[no_mangle]
1639 pub extern "C" fn wgpu_render_bundle_draw_indexed_indirect(
1640 bundle: &mut RenderBundleEncoder,
1641 buffer_id: id::BufferId,
1642 offset: BufferAddress,
1643 ) {
1644 bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1645 buffer_id,
1646 offset,
1647 count: None,
1648 indexed: true,
1649 });
1650 }
1651
1652 #[no_mangle]
1657 pub unsafe extern "C" fn wgpu_render_bundle_push_debug_group(
1658 _bundle: &mut RenderBundleEncoder,
1659 _label: RawString,
1660 ) {
1661 }
1663
1664 #[no_mangle]
1665 pub extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1666 }
1668
1669 #[no_mangle]
1674 pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker(
1675 _bundle: &mut RenderBundleEncoder,
1676 _label: RawString,
1677 ) {
1678 }
1680}