1mod bind;
2mod bundle;
3mod clear;
4mod compute;
5mod draw;
6mod memory_init;
7mod query;
8mod render;
9mod transfer;
10
11use std::slice;
12
13pub(crate) use self::clear::clear_texture;
14pub use self::{
15 bundle::*, clear::ClearError, compute::*, draw::*, query::*, render::*, transfer::*,
16};
17
18use self::memory_init::CommandBufferTextureMemoryActions;
19
20use crate::error::{ErrorFormatter, PrettyError};
21use crate::init_tracker::BufferInitTrackerAction;
22use crate::track::{Tracker, UsageScope};
23use crate::{
24 global::Global,
25 hal_api::HalApi,
26 hub::Token,
27 id,
28 identity::GlobalIdentityHandlerFactory,
29 resource::{Buffer, Texture},
30 storage::Storage,
31 Label, Stored,
32};
33
34use hal::CommandEncoder as _;
35use thiserror::Error;
36
37#[cfg(feature = "trace")]
38use crate::device::trace::Command as TraceCommand;
39
40const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
41
42#[derive(Debug)]
43enum CommandEncoderStatus {
44 Recording,
45 Finished,
46 Error,
47}
48
49struct CommandEncoder<A: hal::Api> {
50 raw: A::CommandEncoder,
51 list: Vec<A::CommandBuffer>,
52 is_open: bool,
53 label: Option<String>,
54}
55
56impl<A: hal::Api> CommandEncoder<A> {
58 fn close_and_swap(&mut self) {
60 if self.is_open {
61 self.is_open = false;
62 let new = unsafe { self.raw.end_encoding().unwrap() };
63 self.list.insert(self.list.len() - 1, new);
64 }
65 }
66
67 fn close(&mut self) {
68 if self.is_open {
69 self.is_open = false;
70 let cmd_buf = unsafe { self.raw.end_encoding().unwrap() };
71 self.list.push(cmd_buf);
72 }
73 }
74
75 fn discard(&mut self) {
76 if self.is_open {
77 self.is_open = false;
78 unsafe { self.raw.discard_encoding() };
79 }
80 }
81
82 fn open(&mut self) -> &mut A::CommandEncoder {
83 if !self.is_open {
84 self.is_open = true;
85 let label = self.label.as_deref();
86 unsafe { self.raw.begin_encoding(label).unwrap() };
87 }
88 &mut self.raw
89 }
90
91 fn open_pass(&mut self, label: Option<&str>) {
92 self.is_open = true;
93 unsafe { self.raw.begin_encoding(label).unwrap() };
94 }
95}
96
97pub struct BakedCommands<A: HalApi> {
98 pub(crate) encoder: A::CommandEncoder,
99 pub(crate) list: Vec<A::CommandBuffer>,
100 pub(crate) trackers: Tracker<A>,
101 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
102 texture_memory_actions: CommandBufferTextureMemoryActions,
103}
104
105pub(crate) struct DestroyedBufferError(pub id::BufferId);
106pub(crate) struct DestroyedTextureError(pub id::TextureId);
107
108pub struct CommandBuffer<A: HalApi> {
109 encoder: CommandEncoder<A>,
110 status: CommandEncoderStatus,
111 pub(crate) device_id: Stored<id::DeviceId>,
112 pub(crate) trackers: Tracker<A>,
113 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
114 texture_memory_actions: CommandBufferTextureMemoryActions,
115 limits: wgt::Limits,
116 support_clear_texture: bool,
117 #[cfg(feature = "trace")]
118 pub(crate) commands: Option<Vec<TraceCommand>>,
119}
120
121impl<A: HalApi> CommandBuffer<A> {
122 pub(crate) fn new(
123 encoder: A::CommandEncoder,
124 device_id: Stored<id::DeviceId>,
125 limits: wgt::Limits,
126 _downlevel: wgt::DownlevelCapabilities,
127 features: wgt::Features,
128 #[cfg(feature = "trace")] enable_tracing: bool,
129 label: &Label,
130 ) -> Self {
131 CommandBuffer {
132 encoder: CommandEncoder {
133 raw: encoder,
134 is_open: false,
135 list: Vec::new(),
136 label: crate::LabelHelpers::borrow_option(label).map(|s| s.to_string()),
137 },
138 status: CommandEncoderStatus::Recording,
139 device_id,
140 trackers: Tracker::new(),
141 buffer_memory_init_actions: Default::default(),
142 texture_memory_actions: Default::default(),
143 limits,
144 support_clear_texture: features.contains(wgt::Features::CLEAR_TEXTURE),
145 #[cfg(feature = "trace")]
146 commands: if enable_tracing {
147 Some(Vec::new())
148 } else {
149 None
150 },
151 }
152 }
153
154 pub(crate) fn insert_barriers_from_tracker(
155 raw: &mut A::CommandEncoder,
156 base: &mut Tracker<A>,
157 head: &Tracker<A>,
158 buffer_guard: &Storage<Buffer<A>, id::BufferId>,
159 texture_guard: &Storage<Texture<A>, id::TextureId>,
160 ) {
161 profiling::scope!("insert_barriers");
162
163 base.buffers.set_from_tracker(&head.buffers);
164 base.textures
165 .set_from_tracker(texture_guard, &head.textures);
166
167 Self::drain_barriers(raw, base, buffer_guard, texture_guard);
168 }
169
170 pub(crate) fn insert_barriers_from_scope(
171 raw: &mut A::CommandEncoder,
172 base: &mut Tracker<A>,
173 head: &UsageScope<A>,
174 buffer_guard: &Storage<Buffer<A>, id::BufferId>,
175 texture_guard: &Storage<Texture<A>, id::TextureId>,
176 ) {
177 profiling::scope!("insert_barriers");
178
179 base.buffers.set_from_usage_scope(&head.buffers);
180 base.textures
181 .set_from_usage_scope(texture_guard, &head.textures);
182
183 Self::drain_barriers(raw, base, buffer_guard, texture_guard);
184 }
185
186 pub(crate) fn drain_barriers(
187 raw: &mut A::CommandEncoder,
188 base: &mut Tracker<A>,
189 buffer_guard: &Storage<Buffer<A>, id::BufferId>,
190 texture_guard: &Storage<Texture<A>, id::TextureId>,
191 ) {
192 profiling::scope!("drain_barriers");
193
194 let buffer_barriers = base.buffers.drain().map(|pending| {
195 let buf = unsafe { &buffer_guard.get_unchecked(pending.id) };
196 pending.into_hal(buf)
197 });
198 let texture_barriers = base.textures.drain().map(|pending| {
199 let tex = unsafe { texture_guard.get_unchecked(pending.id) };
200 pending.into_hal(tex)
201 });
202
203 unsafe {
204 raw.transition_buffers(buffer_barriers);
205 raw.transition_textures(texture_barriers);
206 }
207 }
208}
209
210impl<A: HalApi> CommandBuffer<A> {
211 fn get_encoder_mut(
212 storage: &mut Storage<Self, id::CommandEncoderId>,
213 id: id::CommandEncoderId,
214 ) -> Result<&mut Self, CommandEncoderError> {
215 match storage.get_mut(id) {
216 Ok(cmd_buf) => match cmd_buf.status {
217 CommandEncoderStatus::Recording => Ok(cmd_buf),
218 CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
219 CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
220 },
221 Err(_) => Err(CommandEncoderError::Invalid),
222 }
223 }
224
225 pub fn is_finished(&self) -> bool {
226 match self.status {
227 CommandEncoderStatus::Finished => true,
228 _ => false,
229 }
230 }
231
232 pub(crate) fn into_baked(self) -> BakedCommands<A> {
233 BakedCommands {
234 encoder: self.encoder.raw,
235 list: self.encoder.list,
236 trackers: self.trackers,
237 buffer_memory_init_actions: self.buffer_memory_init_actions,
238 texture_memory_actions: self.texture_memory_actions,
239 }
240 }
241}
242
243impl<A: HalApi> crate::resource::Resource for CommandBuffer<A> {
244 const TYPE: &'static str = "CommandBuffer";
245
246 fn life_guard(&self) -> &crate::LifeGuard {
247 unreachable!()
248 }
249
250 fn label(&self) -> &str {
251 self.encoder.label.as_ref().map_or("", |s| s.as_str())
252 }
253}
254
255#[derive(Copy, Clone, Debug)]
256pub struct BasePassRef<'a, C> {
257 pub label: Option<&'a str>,
258 pub commands: &'a [C],
259 pub dynamic_offsets: &'a [wgt::DynamicOffset],
260 pub string_data: &'a [u8],
261 pub push_constant_data: &'a [u32],
262}
263
264#[doc(hidden)]
276#[derive(Debug)]
277#[cfg_attr(
278 any(feature = "serial-pass", feature = "trace"),
279 derive(serde::Serialize)
280)]
281#[cfg_attr(
282 any(feature = "serial-pass", feature = "replay"),
283 derive(serde::Deserialize)
284)]
285pub struct BasePass<C> {
286 pub label: Option<String>,
287
288 pub commands: Vec<C>,
290
291 pub dynamic_offsets: Vec<wgt::DynamicOffset>,
296
297 pub string_data: Vec<u8>,
302
303 pub push_constant_data: Vec<u32>,
308}
309
310impl<C: Clone> BasePass<C> {
311 fn new(label: &Label) -> Self {
312 Self {
313 label: label.as_ref().map(|cow| cow.to_string()),
314 commands: Vec::new(),
315 dynamic_offsets: Vec::new(),
316 string_data: Vec::new(),
317 push_constant_data: Vec::new(),
318 }
319 }
320
321 #[cfg(feature = "trace")]
322 fn from_ref(base: BasePassRef<C>) -> Self {
323 Self {
324 label: base.label.map(str::to_string),
325 commands: base.commands.to_vec(),
326 dynamic_offsets: base.dynamic_offsets.to_vec(),
327 string_data: base.string_data.to_vec(),
328 push_constant_data: base.push_constant_data.to_vec(),
329 }
330 }
331
332 pub fn as_ref(&self) -> BasePassRef<C> {
333 BasePassRef {
334 label: self.label.as_deref(),
335 commands: &self.commands,
336 dynamic_offsets: &self.dynamic_offsets,
337 string_data: &self.string_data,
338 push_constant_data: &self.push_constant_data,
339 }
340 }
341}
342
343#[derive(Clone, Debug, Error)]
344#[non_exhaustive]
345pub enum CommandEncoderError {
346 #[error("Command encoder is invalid")]
347 Invalid,
348 #[error("Command encoder must be active")]
349 NotRecording,
350}
351
352impl<G: GlobalIdentityHandlerFactory> Global<G> {
353 pub fn command_encoder_finish<A: HalApi>(
354 &self,
355 encoder_id: id::CommandEncoderId,
356 _desc: &wgt::CommandBufferDescriptor<Label>,
357 ) -> (id::CommandBufferId, Option<CommandEncoderError>) {
358 profiling::scope!("CommandEncoder::finish");
359
360 let hub = A::hub(self);
361 let mut token = Token::root();
362 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
363
364 let error = match cmd_buf_guard.get_mut(encoder_id) {
365 Ok(cmd_buf) => match cmd_buf.status {
366 CommandEncoderStatus::Recording => {
367 cmd_buf.encoder.close();
368 cmd_buf.status = CommandEncoderStatus::Finished;
369 log::trace!("Command buffer {:?}", encoder_id);
372 None
373 }
374 CommandEncoderStatus::Finished => Some(CommandEncoderError::NotRecording),
375 CommandEncoderStatus::Error => {
376 cmd_buf.encoder.discard();
377 Some(CommandEncoderError::Invalid)
378 }
379 },
380 Err(_) => Some(CommandEncoderError::Invalid),
381 };
382
383 (encoder_id, error)
384 }
385
386 pub fn command_encoder_push_debug_group<A: HalApi>(
387 &self,
388 encoder_id: id::CommandEncoderId,
389 label: &str,
390 ) -> Result<(), CommandEncoderError> {
391 profiling::scope!("CommandEncoder::push_debug_group");
392
393 let hub = A::hub(self);
394 let mut token = Token::root();
395
396 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
397 let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id)?;
398
399 #[cfg(feature = "trace")]
400 if let Some(ref mut list) = cmd_buf.commands {
401 list.push(TraceCommand::PushDebugGroup(label.to_string()));
402 }
403
404 let cmd_buf_raw = cmd_buf.encoder.open();
405 unsafe {
406 cmd_buf_raw.begin_debug_marker(label);
407 }
408 Ok(())
409 }
410
411 pub fn command_encoder_insert_debug_marker<A: HalApi>(
412 &self,
413 encoder_id: id::CommandEncoderId,
414 label: &str,
415 ) -> Result<(), CommandEncoderError> {
416 profiling::scope!("CommandEncoder::insert_debug_marker");
417
418 let hub = A::hub(self);
419 let mut token = Token::root();
420
421 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
422 let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id)?;
423
424 #[cfg(feature = "trace")]
425 if let Some(ref mut list) = cmd_buf.commands {
426 list.push(TraceCommand::InsertDebugMarker(label.to_string()));
427 }
428
429 let cmd_buf_raw = cmd_buf.encoder.open();
430 unsafe {
431 cmd_buf_raw.insert_debug_marker(label);
432 }
433 Ok(())
434 }
435
436 pub fn command_encoder_pop_debug_group<A: HalApi>(
437 &self,
438 encoder_id: id::CommandEncoderId,
439 ) -> Result<(), CommandEncoderError> {
440 profiling::scope!("CommandEncoder::pop_debug_marker");
441
442 let hub = A::hub(self);
443 let mut token = Token::root();
444
445 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
446 let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id)?;
447
448 #[cfg(feature = "trace")]
449 if let Some(ref mut list) = cmd_buf.commands {
450 list.push(TraceCommand::PopDebugGroup);
451 }
452
453 let cmd_buf_raw = cmd_buf.encoder.open();
454 unsafe {
455 cmd_buf_raw.end_debug_marker();
456 }
457 Ok(())
458 }
459}
460
461fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
462where
463 PushFn: FnMut(u32, &[u32]),
464{
465 let mut count_words = 0_u32;
466 let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
467 while count_words < size_words {
468 let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
469 let size_to_write_words =
470 (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
471
472 push_fn(
473 offset + count_bytes,
474 &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
475 );
476
477 count_words += size_to_write_words;
478 }
479}
480
481#[derive(Debug, Copy, Clone)]
482struct StateChange<T> {
483 last_state: Option<T>,
484}
485
486impl<T: Copy + PartialEq> StateChange<T> {
487 fn new() -> Self {
488 Self { last_state: None }
489 }
490 fn set_and_check_redundant(&mut self, new_state: T) -> bool {
491 let already_set = self.last_state == Some(new_state);
492 self.last_state = Some(new_state);
493 already_set
494 }
495 fn reset(&mut self) {
496 self.last_state = None;
497 }
498}
499
500impl<T: Copy + PartialEq> Default for StateChange<T> {
501 fn default() -> Self {
502 Self::new()
503 }
504}
505
506#[derive(Debug)]
507struct BindGroupStateChange {
508 last_states: [StateChange<id::BindGroupId>; hal::MAX_BIND_GROUPS],
509}
510
511impl BindGroupStateChange {
512 fn new() -> Self {
513 Self {
514 last_states: [StateChange::new(); hal::MAX_BIND_GROUPS],
515 }
516 }
517
518 unsafe fn set_and_check_redundant(
519 &mut self,
520 bind_group_id: id::BindGroupId,
521 index: u32,
522 dynamic_offsets: &mut Vec<u32>,
523 offsets: *const wgt::DynamicOffset,
524 offset_length: usize,
525 ) -> bool {
526 if offset_length == 0 {
528 if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
531 if current_bind_group.set_and_check_redundant(bind_group_id) {
533 return true;
534 }
535 }
536 } else {
537 if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
541 current_bind_group.reset();
542 }
543 dynamic_offsets
544 .extend_from_slice(unsafe { slice::from_raw_parts(offsets, offset_length) });
545 }
546 false
547 }
548 fn reset(&mut self) {
549 self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS];
550 }
551}
552
553impl Default for BindGroupStateChange {
554 fn default() -> Self {
555 Self::new()
556 }
557}
558
559trait MapPassErr<T, O> {
560 fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>;
561}
562
563#[derive(Clone, Copy, Debug, Error)]
564pub enum PassErrorScope {
565 #[error("In a bundle parameter")]
566 Bundle,
567 #[error("In a pass parameter")]
568 Pass(id::CommandEncoderId),
569 #[error("In a set_bind_group command")]
570 SetBindGroup(id::BindGroupId),
571 #[error("In a set_pipeline command")]
572 SetPipelineRender(id::RenderPipelineId),
573 #[error("In a set_pipeline command")]
574 SetPipelineCompute(id::ComputePipelineId),
575 #[error("In a set_push_constant command")]
576 SetPushConstant,
577 #[error("In a set_vertex_buffer command")]
578 SetVertexBuffer(id::BufferId),
579 #[error("In a set_index_buffer command")]
580 SetIndexBuffer(id::BufferId),
581 #[error("In a set_viewport command")]
582 SetViewport,
583 #[error("In a set_scissor_rect command")]
584 SetScissorRect,
585 #[error("In a draw command, indexed:{indexed} indirect:{indirect}")]
586 Draw {
587 indexed: bool,
588 indirect: bool,
589 pipeline: Option<id::RenderPipelineId>,
590 },
591 #[error("While resetting queries after the renderpass was ran")]
592 QueryReset,
593 #[error("In a write_timestamp command")]
594 WriteTimestamp,
595 #[error("In a begin_pipeline_statistics_query command")]
596 BeginPipelineStatisticsQuery,
597 #[error("In a end_pipeline_statistics_query command")]
598 EndPipelineStatisticsQuery,
599 #[error("In a execute_bundle command")]
600 ExecuteBundle,
601 #[error("In a dispatch command, indirect:{indirect}")]
602 Dispatch {
603 indirect: bool,
604 pipeline: Option<id::ComputePipelineId>,
605 },
606 #[error("In a pop_debug_group command")]
607 PopDebugGroup,
608}
609
610impl PrettyError for PassErrorScope {
611 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
612 match *self {
614 Self::Pass(id) => {
615 fmt.command_buffer_label(&id);
616 }
617 Self::SetBindGroup(id) => {
618 fmt.bind_group_label(&id);
619 }
620 Self::SetPipelineRender(id) => {
621 fmt.render_pipeline_label(&id);
622 }
623 Self::SetPipelineCompute(id) => {
624 fmt.compute_pipeline_label(&id);
625 }
626 Self::SetVertexBuffer(id) => {
627 fmt.buffer_label(&id);
628 }
629 Self::SetIndexBuffer(id) => {
630 fmt.buffer_label(&id);
631 }
632 Self::Draw {
633 pipeline: Some(id), ..
634 } => {
635 fmt.render_pipeline_label(&id);
636 }
637 Self::Dispatch {
638 pipeline: Some(id), ..
639 } => {
640 fmt.compute_pipeline_label(&id);
641 }
642 _ => {}
643 }
644 }
645}