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::BeginPipelineStatisticsQuery { .. }
641 | RenderCommand::EndPipelineStatisticsQuery => unimplemented!(),
642 RenderCommand::ExecuteBundle(_)
643 | RenderCommand::SetBlendConstant(_)
644 | RenderCommand::SetStencilReference(_)
645 | RenderCommand::SetViewport { .. }
646 | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
647 }
648 }
649
650 Ok(RenderBundle {
651 base: BasePass {
652 label: desc.label.as_ref().map(|cow| cow.to_string()),
653 commands,
654 dynamic_offsets: state.flat_dynamic_offsets,
655 string_data: Vec::new(),
656 push_constant_data: Vec::new(),
657 },
658 is_depth_read_only: self.is_depth_read_only,
659 is_stencil_read_only: self.is_stencil_read_only,
660 device_id: Stored {
661 value: id::Valid(self.parent_id),
662 ref_count: device.life_guard.add_ref(),
663 },
664 used: state.trackers,
665 buffer_memory_init_actions,
666 texture_memory_init_actions,
667 context: self.context,
668 life_guard: LifeGuard::new(desc.label.borrow_or_default()),
669 })
670 }
671
672 fn check_valid_to_use(
673 &self,
674 device_id: id::Valid<id::DeviceId>,
675 ) -> Result<(), RenderBundleErrorInner> {
676 if device_id.0 != self.parent_id {
677 return Err(RenderBundleErrorInner::NotValidToUse);
678 }
679
680 Ok(())
681 }
682
683 pub fn set_index_buffer(
684 &mut self,
685 buffer_id: id::BufferId,
686 index_format: wgt::IndexFormat,
687 offset: wgt::BufferAddress,
688 size: Option<wgt::BufferSize>,
689 ) {
690 self.base.commands.push(RenderCommand::SetIndexBuffer {
691 buffer_id,
692 index_format,
693 offset,
694 size,
695 });
696 }
697}
698
699#[derive(Clone, Debug, Error)]
701#[non_exhaustive]
702pub enum CreateRenderBundleError {
703 #[error(transparent)]
704 ColorAttachment(#[from] ColorAttachmentError),
705 #[error("Invalid number of samples {0}")]
706 InvalidSampleCount(u32),
707}
708
709#[derive(Clone, Debug, Error)]
711#[non_exhaustive]
712pub enum ExecutionError {
713 #[error("Buffer {0:?} is destroyed")]
714 DestroyedBuffer(id::BufferId),
715 #[error("Using {0} in a render bundle is not implemented")]
716 Unimplemented(&'static str),
717}
718impl PrettyError for ExecutionError {
719 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
720 fmt.error(self);
721 match *self {
722 Self::DestroyedBuffer(id) => {
723 fmt.buffer_label(&id);
724 }
725 Self::Unimplemented(_reason) => {}
726 };
727 }
728}
729
730pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
731
732pub struct RenderBundle<A: HalApi> {
736 base: BasePass<RenderCommand>,
739 pub(super) is_depth_read_only: bool,
740 pub(super) is_stencil_read_only: bool,
741 pub(crate) device_id: Stored<id::DeviceId>,
742 pub(crate) used: RenderBundleScope<A>,
743 pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
744 pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
745 pub(super) context: RenderPassContext,
746 pub(crate) life_guard: LifeGuard,
747}
748
749#[cfg(any(
750 not(target_arch = "wasm32"),
751 all(
752 feature = "fragile-send-sync-non-atomic-wasm",
753 not(target_feature = "atomics")
754 )
755))]
756unsafe impl<A: HalApi> Send for RenderBundle<A> {}
757#[cfg(any(
758 not(target_arch = "wasm32"),
759 all(
760 feature = "fragile-send-sync-non-atomic-wasm",
761 not(target_feature = "atomics")
762 )
763))]
764unsafe impl<A: HalApi> Sync for RenderBundle<A> {}
765
766impl<A: HalApi> RenderBundle<A> {
767 pub(super) unsafe fn execute(
777 &self,
778 raw: &mut A::CommandEncoder,
779 pipeline_layout_guard: &Storage<
780 crate::binding_model::PipelineLayout<A>,
781 id::PipelineLayoutId,
782 >,
783 bind_group_guard: &Storage<crate::binding_model::BindGroup<A>, id::BindGroupId>,
784 pipeline_guard: &Storage<crate::pipeline::RenderPipeline<A>, id::RenderPipelineId>,
785 buffer_guard: &Storage<crate::resource::Buffer<A>, id::BufferId>,
786 ) -> Result<(), ExecutionError> {
787 let mut offsets = self.base.dynamic_offsets.as_slice();
788 let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
789 if let Some(ref label) = self.base.label {
790 unsafe { raw.begin_debug_marker(label) };
791 }
792
793 for command in self.base.commands.iter() {
794 match *command {
795 RenderCommand::SetBindGroup {
796 index,
797 num_dynamic_offsets,
798 bind_group_id,
799 } => {
800 let bind_group = bind_group_guard.get(bind_group_id).unwrap();
801 unsafe {
802 raw.set_bind_group(
803 &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw,
804 index,
805 &bind_group.raw,
806 &offsets[..num_dynamic_offsets as usize],
807 )
808 };
809 offsets = &offsets[num_dynamic_offsets as usize..];
810 }
811 RenderCommand::SetPipeline(pipeline_id) => {
812 let pipeline = pipeline_guard.get(pipeline_id).unwrap();
813 unsafe { raw.set_render_pipeline(&pipeline.raw) };
814
815 pipeline_layout_id = Some(pipeline.layout_id.value);
816 }
817 RenderCommand::SetIndexBuffer {
818 buffer_id,
819 index_format,
820 offset,
821 size,
822 } => {
823 let buffer = buffer_guard
824 .get(buffer_id)
825 .unwrap()
826 .raw
827 .as_ref()
828 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
829 let bb = hal::BufferBinding {
830 buffer,
831 offset,
832 size,
833 };
834 unsafe { raw.set_index_buffer(bb, index_format) };
835 }
836 RenderCommand::SetVertexBuffer {
837 slot,
838 buffer_id,
839 offset,
840 size,
841 } => {
842 let buffer = buffer_guard
843 .get(buffer_id)
844 .unwrap()
845 .raw
846 .as_ref()
847 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
848 let bb = hal::BufferBinding {
849 buffer,
850 offset,
851 size,
852 };
853 unsafe { raw.set_vertex_buffer(slot, bb) };
854 }
855 RenderCommand::SetPushConstant {
856 stages,
857 offset,
858 size_bytes,
859 values_offset,
860 } => {
861 let pipeline_layout_id = pipeline_layout_id.unwrap();
862 let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
863
864 if let Some(values_offset) = values_offset {
865 let values_end_offset =
866 (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
867 let data_slice = &self.base.push_constant_data
868 [(values_offset as usize)..values_end_offset];
869
870 unsafe {
871 raw.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice)
872 }
873 } else {
874 super::push_constant_clear(
875 offset,
876 size_bytes,
877 |clear_offset, clear_data| {
878 unsafe {
879 raw.set_push_constants(
880 &pipeline_layout.raw,
881 stages,
882 clear_offset,
883 clear_data,
884 )
885 };
886 },
887 );
888 }
889 }
890 RenderCommand::Draw {
891 vertex_count,
892 instance_count,
893 first_vertex,
894 first_instance,
895 } => {
896 unsafe { raw.draw(first_vertex, vertex_count, first_instance, instance_count) };
897 }
898 RenderCommand::DrawIndexed {
899 index_count,
900 instance_count,
901 first_index,
902 base_vertex,
903 first_instance,
904 } => {
905 unsafe {
906 raw.draw_indexed(
907 first_index,
908 index_count,
909 base_vertex,
910 first_instance,
911 instance_count,
912 )
913 };
914 }
915 RenderCommand::MultiDrawIndirect {
916 buffer_id,
917 offset,
918 count: None,
919 indexed: false,
920 } => {
921 let buffer = buffer_guard
922 .get(buffer_id)
923 .unwrap()
924 .raw
925 .as_ref()
926 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
927 unsafe { raw.draw_indirect(buffer, offset, 1) };
928 }
929 RenderCommand::MultiDrawIndirect {
930 buffer_id,
931 offset,
932 count: None,
933 indexed: true,
934 } => {
935 let buffer = buffer_guard
936 .get(buffer_id)
937 .unwrap()
938 .raw
939 .as_ref()
940 .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
941 unsafe { raw.draw_indexed_indirect(buffer, offset, 1) };
942 }
943 RenderCommand::MultiDrawIndirect { .. }
944 | RenderCommand::MultiDrawIndirectCount { .. } => {
945 return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
946 }
947 RenderCommand::PushDebugGroup { .. }
948 | RenderCommand::InsertDebugMarker { .. }
949 | RenderCommand::PopDebugGroup => {
950 return Err(ExecutionError::Unimplemented("debug-markers"))
951 }
952 RenderCommand::WriteTimestamp { .. }
953 | RenderCommand::BeginPipelineStatisticsQuery { .. }
954 | RenderCommand::EndPipelineStatisticsQuery => {
955 return Err(ExecutionError::Unimplemented("queries"))
956 }
957 RenderCommand::ExecuteBundle(_)
958 | RenderCommand::SetBlendConstant(_)
959 | RenderCommand::SetStencilReference(_)
960 | RenderCommand::SetViewport { .. }
961 | RenderCommand::SetScissor(_) => unreachable!(),
962 }
963 }
964
965 if let Some(_) = self.base.label {
966 unsafe { raw.end_debug_marker() };
967 }
968
969 Ok(())
970 }
971}
972
973impl<A: HalApi> Resource for RenderBundle<A> {
974 const TYPE: &'static str = "RenderBundle";
975
976 fn life_guard(&self) -> &LifeGuard {
977 &self.life_guard
978 }
979}
980
981#[derive(Debug)]
987struct IndexState {
988 buffer: id::BufferId,
989 format: wgt::IndexFormat,
990 range: Range<wgt::BufferAddress>,
991 is_dirty: bool,
992}
993
994impl IndexState {
995 fn limit(&self) -> u32 {
999 let bytes_per_index = match self.format {
1000 wgt::IndexFormat::Uint16 => 2,
1001 wgt::IndexFormat::Uint32 => 4,
1002 };
1003 ((self.range.end - self.range.start) / bytes_per_index) as u32
1004 }
1005
1006 fn flush(&mut self) -> Option<RenderCommand> {
1009 if self.is_dirty {
1010 self.is_dirty = false;
1011 Some(RenderCommand::SetIndexBuffer {
1012 buffer_id: self.buffer,
1013 index_format: self.format,
1014 offset: self.range.start,
1015 size: wgt::BufferSize::new(self.range.end - self.range.start),
1016 })
1017 } else {
1018 None
1019 }
1020 }
1021}
1022
1023#[derive(Debug)]
1033struct VertexState {
1034 buffer: id::BufferId,
1035 range: Range<wgt::BufferAddress>,
1036 is_dirty: bool,
1037}
1038
1039impl VertexState {
1040 fn new(buffer: id::BufferId, range: Range<wgt::BufferAddress>) -> Self {
1041 Self {
1042 buffer,
1043 range,
1044 is_dirty: true,
1045 }
1046 }
1047
1048 fn flush(&mut self, slot: u32) -> Option<RenderCommand> {
1052 if self.is_dirty {
1053 self.is_dirty = false;
1054 Some(RenderCommand::SetVertexBuffer {
1055 slot,
1056 buffer_id: self.buffer,
1057 offset: self.range.start,
1058 size: wgt::BufferSize::new(self.range.end - self.range.start),
1059 })
1060 } else {
1061 None
1062 }
1063 }
1064}
1065
1066#[derive(Debug)]
1068struct BindState {
1069 bind_group_id: id::BindGroupId,
1071
1072 layout_id: id::Valid<id::BindGroupLayoutId>,
1074
1075 dynamic_offsets: Range<usize>,
1078
1079 is_dirty: bool,
1082}
1083
1084#[derive(Debug)]
1085struct VertexLimitState {
1086 vertex_limit: u32,
1088 vertex_limit_slot: u32,
1090 instance_limit: u32,
1092 instance_limit_slot: u32,
1094}
1095
1096struct PipelineState {
1098 id: id::RenderPipelineId,
1100
1101 layout_id: id::Valid<id::PipelineLayoutId>,
1103
1104 steps: Vec<pipeline::VertexStep>,
1107
1108 push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
1111
1112 used_bind_groups: usize,
1114}
1115
1116impl PipelineState {
1117 fn new<A: HalApi>(
1118 pipeline_id: id::RenderPipelineId,
1119 pipeline: &pipeline::RenderPipeline<A>,
1120 layout: &binding_model::PipelineLayout<A>,
1121 ) -> Self {
1122 Self {
1123 id: pipeline_id,
1124 layout_id: pipeline.layout_id.value,
1125 steps: pipeline.vertex_steps.to_vec(),
1126 push_constant_ranges: layout.push_constant_ranges.iter().cloned().collect(),
1127 used_bind_groups: layout.bind_group_layout_ids.len(),
1128 }
1129 }
1130
1131 fn zero_push_constants(&self) -> Option<impl Iterator<Item = RenderCommand>> {
1134 if !self.push_constant_ranges.is_empty() {
1135 let nonoverlapping_ranges =
1136 super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges);
1137
1138 Some(
1139 nonoverlapping_ranges
1140 .into_iter()
1141 .map(|range| RenderCommand::SetPushConstant {
1142 stages: range.stages,
1143 offset: range.range.start,
1144 size_bytes: range.range.end - range.range.start,
1145 values_offset: None, }),
1147 )
1148 } else {
1149 None
1150 }
1151 }
1152}
1153
1154struct State<A: HalApi> {
1165 trackers: RenderBundleScope<A>,
1167
1168 pipeline: Option<PipelineState>,
1170
1171 bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>,
1173
1174 vertex: ArrayVec<Option<VertexState>, { hal::MAX_VERTEX_BUFFERS }>,
1176
1177 index: Option<IndexState>,
1180
1181 flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1188}
1189
1190impl<A: HalApi> State<A> {
1191 fn vertex_limits(&self, pipeline: &PipelineState) -> VertexLimitState {
1192 let mut vert_state = VertexLimitState {
1193 vertex_limit: u32::MAX,
1194 vertex_limit_slot: 0,
1195 instance_limit: u32::MAX,
1196 instance_limit_slot: 0,
1197 };
1198 for (idx, (vbs, step)) in self.vertex.iter().zip(&pipeline.steps).enumerate() {
1199 if let Some(ref vbs) = *vbs {
1200 let limit = ((vbs.range.end - vbs.range.start) / step.stride) as u32;
1201 match step.mode {
1202 wgt::VertexStepMode::Vertex => {
1203 if limit < vert_state.vertex_limit {
1204 vert_state.vertex_limit = limit;
1205 vert_state.vertex_limit_slot = idx as _;
1206 }
1207 }
1208 wgt::VertexStepMode::Instance => {
1209 if limit < vert_state.instance_limit {
1210 vert_state.instance_limit = limit;
1211 vert_state.instance_limit_slot = idx as _;
1212 }
1213 }
1214 }
1215 }
1216 }
1217 vert_state
1218 }
1219
1220 fn pipeline_id(&self) -> Option<id::RenderPipelineId> {
1222 self.pipeline.as_ref().map(|p| p.id)
1223 }
1224
1225 fn pipeline(&self, scope: PassErrorScope) -> Result<&PipelineState, RenderBundleError> {
1227 self.pipeline
1228 .as_ref()
1229 .ok_or(DrawError::MissingPipeline)
1230 .map_pass_err(scope)
1231 }
1232
1233 fn invalidate_bind_group_from(&mut self, index: usize) {
1235 for contents in self.bind[index..].iter_mut().flatten() {
1236 contents.is_dirty = true;
1237 }
1238 }
1239
1240 fn set_bind_group(
1241 &mut self,
1242 slot: u32,
1243 bind_group_id: id::BindGroupId,
1244 layout_id: id::Valid<id::BindGroupLayoutId>,
1245 dynamic_offsets: Range<usize>,
1246 ) {
1247 if dynamic_offsets.is_empty() {
1251 if let Some(ref contents) = self.bind[slot as usize] {
1252 if contents.bind_group_id == bind_group_id {
1253 return;
1254 }
1255 }
1256 }
1257
1258 self.bind[slot as usize] = Some(BindState {
1260 bind_group_id,
1261 layout_id,
1262 dynamic_offsets,
1263 is_dirty: true,
1264 });
1265
1266 self.invalidate_bind_group_from(slot as usize + 1);
1269 }
1270
1271 fn invalidate_bind_groups(
1285 &mut self,
1286 new: &PipelineState,
1287 layout: &binding_model::PipelineLayout<A>,
1288 ) {
1289 match self.pipeline {
1290 None => {
1291 self.invalidate_bind_group_from(0);
1293 }
1294 Some(ref old) => {
1295 if old.id == new.id {
1296 return;
1299 }
1300
1301 if old.push_constant_ranges != new.push_constant_ranges {
1303 self.invalidate_bind_group_from(0);
1304 } else {
1305 let first_changed = self
1306 .bind
1307 .iter()
1308 .zip(&layout.bind_group_layout_ids)
1309 .position(|(entry, &layout_id)| match *entry {
1310 Some(ref contents) => contents.layout_id != layout_id,
1311 None => false,
1312 });
1313 if let Some(slot) = first_changed {
1314 self.invalidate_bind_group_from(slot);
1315 }
1316 }
1317 }
1318 }
1319 }
1320
1321 fn set_index_buffer(
1323 &mut self,
1324 buffer: id::BufferId,
1325 format: wgt::IndexFormat,
1326 range: Range<wgt::BufferAddress>,
1327 ) {
1328 match self.index {
1329 Some(ref current)
1330 if current.buffer == buffer
1331 && current.format == format
1332 && current.range == range =>
1333 {
1334 return
1335 }
1336 _ => (),
1337 }
1338
1339 self.index = Some(IndexState {
1340 buffer,
1341 format,
1342 range,
1343 is_dirty: true,
1344 });
1345 }
1346
1347 fn flush_index(&mut self) -> Option<RenderCommand> {
1350 self.index.as_mut().and_then(|index| index.flush())
1351 }
1352
1353 fn flush_vertices(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
1354 self.vertex
1355 .iter_mut()
1356 .enumerate()
1357 .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)))
1358 }
1359
1360 fn flush_binds(
1362 &mut self,
1363 used_bind_groups: usize,
1364 dynamic_offsets: &[wgt::DynamicOffset],
1365 ) -> impl Iterator<Item = RenderCommand> + '_ {
1366 for contents in self.bind[..used_bind_groups].iter().flatten() {
1368 if contents.is_dirty {
1369 self.flat_dynamic_offsets
1370 .extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]);
1371 }
1372 }
1373
1374 self.bind[..used_bind_groups]
1377 .iter_mut()
1378 .enumerate()
1379 .flat_map(|(i, entry)| {
1380 if let Some(ref mut contents) = *entry {
1381 if contents.is_dirty {
1382 contents.is_dirty = false;
1383 let offsets = &contents.dynamic_offsets;
1384 return Some(RenderCommand::SetBindGroup {
1385 index: i.try_into().unwrap(),
1386 bind_group_id: contents.bind_group_id,
1387 num_dynamic_offsets: (offsets.end - offsets.start) as u8,
1388 });
1389 }
1390 }
1391 None
1392 })
1393 }
1394}
1395
1396#[derive(Clone, Debug, Error)]
1398pub(super) enum RenderBundleErrorInner {
1399 #[error("Resource is not valid to use with this render bundle because the resource and the bundle come from different devices")]
1400 NotValidToUse,
1401 #[error(transparent)]
1402 Device(#[from] DeviceError),
1403 #[error(transparent)]
1404 RenderCommand(RenderCommandError),
1405 #[error(transparent)]
1406 Draw(#[from] DrawError),
1407 #[error(transparent)]
1408 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1409}
1410
1411impl<T> From<T> for RenderBundleErrorInner
1412where
1413 T: Into<RenderCommandError>,
1414{
1415 fn from(t: T) -> Self {
1416 Self::RenderCommand(t.into())
1417 }
1418}
1419
1420#[derive(Clone, Debug, Error)]
1422#[error("{scope}")]
1423pub struct RenderBundleError {
1424 pub scope: PassErrorScope,
1425 #[source]
1426 inner: RenderBundleErrorInner,
1427}
1428
1429impl RenderBundleError {
1430 pub(crate) const INVALID_DEVICE: Self = RenderBundleError {
1431 scope: PassErrorScope::Bundle,
1432 inner: RenderBundleErrorInner::Device(DeviceError::Invalid),
1433 };
1434}
1435impl PrettyError for RenderBundleError {
1436 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
1437 fmt.error(self);
1440 self.scope.fmt_pretty(fmt);
1441 }
1442}
1443
1444impl<T, E> MapPassErr<T, RenderBundleError> for Result<T, E>
1445where
1446 E: Into<RenderBundleErrorInner>,
1447{
1448 fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderBundleError> {
1449 self.map_err(|inner| RenderBundleError {
1450 scope,
1451 inner: inner.into(),
1452 })
1453 }
1454}
1455
1456pub mod bundle_ffi {
1457 use super::{RenderBundleEncoder, RenderCommand};
1458 use crate::{id, RawString};
1459 use std::{convert::TryInto, slice};
1460 use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1461
1462 #[no_mangle]
1467 pub unsafe extern "C" fn wgpu_render_bundle_set_bind_group(
1468 bundle: &mut RenderBundleEncoder,
1469 index: u32,
1470 bind_group_id: id::BindGroupId,
1471 offsets: *const DynamicOffset,
1472 offset_length: usize,
1473 ) {
1474 let redundant = unsafe {
1475 bundle.current_bind_groups.set_and_check_redundant(
1476 bind_group_id,
1477 index,
1478 &mut bundle.base.dynamic_offsets,
1479 offsets,
1480 offset_length,
1481 )
1482 };
1483
1484 if redundant {
1485 return;
1486 }
1487
1488 bundle.base.commands.push(RenderCommand::SetBindGroup {
1489 index,
1490 num_dynamic_offsets: offset_length.try_into().unwrap(),
1491 bind_group_id,
1492 });
1493 }
1494
1495 #[no_mangle]
1496 pub extern "C" fn wgpu_render_bundle_set_pipeline(
1497 bundle: &mut RenderBundleEncoder,
1498 pipeline_id: id::RenderPipelineId,
1499 ) {
1500 if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1501 return;
1502 }
1503
1504 bundle
1505 .base
1506 .commands
1507 .push(RenderCommand::SetPipeline(pipeline_id));
1508 }
1509
1510 #[no_mangle]
1511 pub extern "C" fn wgpu_render_bundle_set_vertex_buffer(
1512 bundle: &mut RenderBundleEncoder,
1513 slot: u32,
1514 buffer_id: id::BufferId,
1515 offset: BufferAddress,
1516 size: Option<BufferSize>,
1517 ) {
1518 bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1519 slot,
1520 buffer_id,
1521 offset,
1522 size,
1523 });
1524 }
1525
1526 #[no_mangle]
1527 pub extern "C" fn wgpu_render_bundle_set_index_buffer(
1528 encoder: &mut RenderBundleEncoder,
1529 buffer: id::BufferId,
1530 index_format: IndexFormat,
1531 offset: BufferAddress,
1532 size: Option<BufferSize>,
1533 ) {
1534 encoder.set_index_buffer(buffer, index_format, offset, size);
1535 }
1536
1537 #[no_mangle]
1542 pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants(
1543 pass: &mut RenderBundleEncoder,
1544 stages: wgt::ShaderStages,
1545 offset: u32,
1546 size_bytes: u32,
1547 data: *const u8,
1548 ) {
1549 assert_eq!(
1550 offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1551 0,
1552 "Push constant offset must be aligned to 4 bytes."
1553 );
1554 assert_eq!(
1555 size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1556 0,
1557 "Push constant size must be aligned to 4 bytes."
1558 );
1559 let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1560 let value_offset = pass.base.push_constant_data.len().try_into().expect(
1561 "Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.",
1562 );
1563
1564 pass.base.push_constant_data.extend(
1565 data_slice
1566 .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
1567 .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1568 );
1569
1570 pass.base.commands.push(RenderCommand::SetPushConstant {
1571 stages,
1572 offset,
1573 size_bytes,
1574 values_offset: Some(value_offset),
1575 });
1576 }
1577
1578 #[no_mangle]
1579 pub extern "C" fn wgpu_render_bundle_draw(
1580 bundle: &mut RenderBundleEncoder,
1581 vertex_count: u32,
1582 instance_count: u32,
1583 first_vertex: u32,
1584 first_instance: u32,
1585 ) {
1586 bundle.base.commands.push(RenderCommand::Draw {
1587 vertex_count,
1588 instance_count,
1589 first_vertex,
1590 first_instance,
1591 });
1592 }
1593
1594 #[no_mangle]
1595 pub extern "C" fn wgpu_render_bundle_draw_indexed(
1596 bundle: &mut RenderBundleEncoder,
1597 index_count: u32,
1598 instance_count: u32,
1599 first_index: u32,
1600 base_vertex: i32,
1601 first_instance: u32,
1602 ) {
1603 bundle.base.commands.push(RenderCommand::DrawIndexed {
1604 index_count,
1605 instance_count,
1606 first_index,
1607 base_vertex,
1608 first_instance,
1609 });
1610 }
1611
1612 #[no_mangle]
1613 pub extern "C" fn wgpu_render_bundle_draw_indirect(
1614 bundle: &mut RenderBundleEncoder,
1615 buffer_id: id::BufferId,
1616 offset: BufferAddress,
1617 ) {
1618 bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1619 buffer_id,
1620 offset,
1621 count: None,
1622 indexed: false,
1623 });
1624 }
1625
1626 #[no_mangle]
1627 pub extern "C" fn wgpu_render_bundle_draw_indexed_indirect(
1628 bundle: &mut RenderBundleEncoder,
1629 buffer_id: id::BufferId,
1630 offset: BufferAddress,
1631 ) {
1632 bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1633 buffer_id,
1634 offset,
1635 count: None,
1636 indexed: true,
1637 });
1638 }
1639
1640 #[no_mangle]
1645 pub unsafe extern "C" fn wgpu_render_bundle_push_debug_group(
1646 _bundle: &mut RenderBundleEncoder,
1647 _label: RawString,
1648 ) {
1649 }
1651
1652 #[no_mangle]
1653 pub extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1654 }
1656
1657 #[no_mangle]
1662 pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker(
1663 _bundle: &mut RenderBundleEncoder,
1664 _label: RawString,
1665 ) {
1666 }
1668}