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