wgpu_core/command/
mod.rs

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
56//TODO: handle errors better
57impl<A: hal::Api> CommandEncoder<A> {
58    /// Closes the live encoder
59    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/// A stream of commands for a render pass or compute pass.
265///
266/// This also contains side tables referred to by certain commands,
267/// like dynamic offsets for [`SetBindGroup`] or string data for
268/// [`InsertDebugMarker`].
269///
270/// Render passes use `BasePass<RenderCommand>`, whereas compute
271/// passes use `BasePass<ComputeCommand>`.
272///
273/// [`SetBindGroup`]: RenderCommand::SetBindGroup
274/// [`InsertDebugMarker`]: RenderCommand::InsertDebugMarker
275#[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    /// The stream of commands.
289    pub commands: Vec<C>,
290
291    /// Dynamic offsets consumed by [`SetBindGroup`] commands in `commands`.
292    ///
293    /// Each successive `SetBindGroup` consumes the next
294    /// [`num_dynamic_offsets`] values from this list.
295    pub dynamic_offsets: Vec<wgt::DynamicOffset>,
296
297    /// Strings used by debug instructions.
298    ///
299    /// Each successive [`PushDebugGroup`] or [`InsertDebugMarker`]
300    /// instruction consumes the next `len` bytes from this vector.
301    pub string_data: Vec<u8>,
302
303    /// Data used by `SetPushConstant` instructions.
304    ///
305    /// See the documentation for [`RenderCommand::SetPushConstant`]
306    /// and [`ComputeCommand::SetPushConstant`] for details.
307    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                    //Note: if we want to stop tracking the swapchain texture view,
370                    // this is the place to do it.
371                    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        // For now never deduplicate bind groups with dynamic offsets.
527        if offset_length == 0 {
528            // If this get returns None, that means we're well over the limit,
529            // so let the call through to get a proper error
530            if let Some(current_bind_group) = self.last_states.get_mut(index as usize) {
531                // Bail out if we're binding the same bind group.
532                if current_bind_group.set_and_check_redundant(bind_group_id) {
533                    return true;
534                }
535            }
536        } else {
537            // We intentionally remove the memory of this bind group if we have dynamic offsets,
538            // such that if you try to bind this bind group later with _no_ dynamic offsets it
539            // tries to bind it again and gives a proper validation error.
540            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        // This error is not in the error chain, only notes are needed
613        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}