1 mod bind;
2 mod bundle;
3 mod clear;
4 mod compute;
5 mod draw;
6 mod memory_init;
7 mod query;
8 mod render;
9 mod transfer;
10
11 pub(crate) use self::clear::clear_texture_no_device;
12 pub use self::{
13 bundle::*, clear::ClearError, compute::*, draw::*, query::*, render::*, transfer::*,
14 };
15
16 use self::memory_init::CommandBufferTextureMemoryActions;
17
18 use crate::error::{ErrorFormatter, PrettyError};
19 use crate::init_tracker::BufferInitTrackerAction;
20 use crate::{
21 hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token},
22 id,
23 resource::{Buffer, Texture},
24 track::{BufferState, ResourceTracker, TextureState, TrackerSet},
25 Label, Stored,
26 };
27
28 use hal::CommandEncoder as _;
29 use thiserror::Error;
30
31 #[cfg(feature = "trace")]
32 use crate::device::trace::Command as TraceCommand;
33
34 const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
35
36 #[derive(Debug)]
37 enum CommandEncoderStatus {
38 Recording,
39 Finished,
40 Error,
41 }
42
43 struct CommandEncoder<A: hal::Api> {
44 raw: A::CommandEncoder,
45 list: Vec<A::CommandBuffer>,
46 is_open: bool,
47 label: Option<String>,
48 }
49
50 //TODO: handle errors better
51 impl<A: hal::Api> CommandEncoder<A> {
close(&mut self)52 fn close(&mut self) {
53 if self.is_open {
54 self.is_open = false;
55 let cmd_buf = unsafe { self.raw.end_encoding().unwrap() };
56 self.list.push(cmd_buf);
57 }
58 }
59
discard(&mut self)60 fn discard(&mut self) {
61 if self.is_open {
62 self.is_open = false;
63 unsafe { self.raw.discard_encoding() };
64 }
65 }
66
open(&mut self) -> &mut A::CommandEncoder67 fn open(&mut self) -> &mut A::CommandEncoder {
68 if !self.is_open {
69 self.is_open = true;
70 let label = self.label.as_deref();
71 unsafe { self.raw.begin_encoding(label).unwrap() };
72 }
73 &mut self.raw
74 }
75
open_pass(&mut self, label: Option<&str>)76 fn open_pass(&mut self, label: Option<&str>) {
77 self.is_open = true;
78 unsafe { self.raw.begin_encoding(label).unwrap() };
79 }
80 }
81
82 pub struct BakedCommands<A: hal::Api> {
83 pub(crate) encoder: A::CommandEncoder,
84 pub(crate) list: Vec<A::CommandBuffer>,
85 pub(crate) trackers: TrackerSet,
86 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
87 texture_memory_actions: CommandBufferTextureMemoryActions,
88 }
89
90 pub(crate) struct DestroyedBufferError(pub id::BufferId);
91 pub(crate) struct DestroyedTextureError(pub id::TextureId);
92
93 pub struct CommandBuffer<A: hal::Api> {
94 encoder: CommandEncoder<A>,
95 status: CommandEncoderStatus,
96 pub(crate) device_id: Stored<id::DeviceId>,
97 pub(crate) trackers: TrackerSet,
98 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
99 texture_memory_actions: CommandBufferTextureMemoryActions,
100 limits: wgt::Limits,
101 support_clear_texture: bool,
102 #[cfg(feature = "trace")]
103 pub(crate) commands: Option<Vec<TraceCommand>>,
104 }
105
106 impl<A: HalApi> CommandBuffer<A> {
new( encoder: A::CommandEncoder, device_id: Stored<id::DeviceId>, limits: wgt::Limits, _downlevel: wgt::DownlevelCapabilities, features: wgt::Features, #[cfg(feature = "trace")] enable_tracing: bool, label: &Label, ) -> Self107 pub(crate) fn new(
108 encoder: A::CommandEncoder,
109 device_id: Stored<id::DeviceId>,
110 limits: wgt::Limits,
111 _downlevel: wgt::DownlevelCapabilities,
112 features: wgt::Features,
113 #[cfg(feature = "trace")] enable_tracing: bool,
114 label: &Label,
115 ) -> Self {
116 CommandBuffer {
117 encoder: CommandEncoder {
118 raw: encoder,
119 is_open: false,
120 list: Vec::new(),
121 label: crate::LabelHelpers::borrow_option(label).map(|s| s.to_string()),
122 },
123 status: CommandEncoderStatus::Recording,
124 device_id,
125 trackers: TrackerSet::new(A::VARIANT),
126 buffer_memory_init_actions: Default::default(),
127 texture_memory_actions: Default::default(),
128 limits,
129 support_clear_texture: features.contains(wgt::Features::CLEAR_TEXTURE),
130 #[cfg(feature = "trace")]
131 commands: if enable_tracing {
132 Some(Vec::new())
133 } else {
134 None
135 },
136 }
137 }
138
insert_barriers( raw: &mut A::CommandEncoder, base: &mut TrackerSet, head_buffers: &ResourceTracker<BufferState>, head_textures: &ResourceTracker<TextureState>, buffer_guard: &Storage<Buffer<A>, id::BufferId>, texture_guard: &Storage<Texture<A>, id::TextureId>, )139 pub(crate) fn insert_barriers(
140 raw: &mut A::CommandEncoder,
141 base: &mut TrackerSet,
142 head_buffers: &ResourceTracker<BufferState>,
143 head_textures: &ResourceTracker<TextureState>,
144 buffer_guard: &Storage<Buffer<A>, id::BufferId>,
145 texture_guard: &Storage<Texture<A>, id::TextureId>,
146 ) {
147 profiling::scope!("insert_barriers");
148 debug_assert_eq!(A::VARIANT, base.backend());
149
150 let buffer_barriers = base.buffers.merge_replace(head_buffers).map(|pending| {
151 let buf = &buffer_guard[pending.id];
152 pending.into_hal(buf)
153 });
154 let texture_barriers = base.textures.merge_replace(head_textures).map(|pending| {
155 let tex = &texture_guard[pending.id];
156 pending.into_hal(tex)
157 });
158
159 unsafe {
160 raw.transition_buffers(buffer_barriers);
161 raw.transition_textures(texture_barriers);
162 }
163 }
164 }
165
166 impl<A: hal::Api> CommandBuffer<A> {
get_encoder_mut( storage: &mut Storage<Self, id::CommandEncoderId>, id: id::CommandEncoderId, ) -> Result<&mut Self, CommandEncoderError>167 fn get_encoder_mut(
168 storage: &mut Storage<Self, id::CommandEncoderId>,
169 id: id::CommandEncoderId,
170 ) -> Result<&mut Self, CommandEncoderError> {
171 match storage.get_mut(id) {
172 Ok(cmd_buf) => match cmd_buf.status {
173 CommandEncoderStatus::Recording => Ok(cmd_buf),
174 CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording),
175 CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid),
176 },
177 Err(_) => Err(CommandEncoderError::Invalid),
178 }
179 }
180
is_finished(&self) -> bool181 pub fn is_finished(&self) -> bool {
182 match self.status {
183 CommandEncoderStatus::Finished => true,
184 _ => false,
185 }
186 }
187
into_baked(self) -> BakedCommands<A>188 pub(crate) fn into_baked(self) -> BakedCommands<A> {
189 BakedCommands {
190 encoder: self.encoder.raw,
191 list: self.encoder.list,
192 trackers: self.trackers,
193 buffer_memory_init_actions: self.buffer_memory_init_actions,
194 texture_memory_actions: self.texture_memory_actions,
195 }
196 }
197 }
198
199 impl<A: hal::Api> crate::hub::Resource for CommandBuffer<A> {
200 const TYPE: &'static str = "CommandBuffer";
201
life_guard(&self) -> &crate::LifeGuard202 fn life_guard(&self) -> &crate::LifeGuard {
203 unreachable!()
204 }
205
label(&self) -> &str206 fn label(&self) -> &str {
207 self.encoder.label.as_ref().map_or("", |s| s.as_str())
208 }
209 }
210
211 #[derive(Copy, Clone, Debug)]
212 pub struct BasePassRef<'a, C> {
213 pub label: Option<&'a str>,
214 pub commands: &'a [C],
215 pub dynamic_offsets: &'a [wgt::DynamicOffset],
216 pub string_data: &'a [u8],
217 pub push_constant_data: &'a [u32],
218 }
219
220 #[doc(hidden)]
221 #[derive(Debug)]
222 #[cfg_attr(
223 any(feature = "serial-pass", feature = "trace"),
224 derive(serde::Serialize)
225 )]
226 #[cfg_attr(
227 any(feature = "serial-pass", feature = "replay"),
228 derive(serde::Deserialize)
229 )]
230 pub struct BasePass<C> {
231 pub label: Option<String>,
232 pub commands: Vec<C>,
233 pub dynamic_offsets: Vec<wgt::DynamicOffset>,
234 pub string_data: Vec<u8>,
235 pub push_constant_data: Vec<u32>,
236 }
237
238 impl<C: Clone> BasePass<C> {
new(label: &Label) -> Self239 fn new(label: &Label) -> Self {
240 Self {
241 label: label.as_ref().map(|cow| cow.to_string()),
242 commands: Vec::new(),
243 dynamic_offsets: Vec::new(),
244 string_data: Vec::new(),
245 push_constant_data: Vec::new(),
246 }
247 }
248
249 #[cfg(feature = "trace")]
from_ref(base: BasePassRef<C>) -> Self250 fn from_ref(base: BasePassRef<C>) -> Self {
251 Self {
252 label: base.label.map(str::to_string),
253 commands: base.commands.to_vec(),
254 dynamic_offsets: base.dynamic_offsets.to_vec(),
255 string_data: base.string_data.to_vec(),
256 push_constant_data: base.push_constant_data.to_vec(),
257 }
258 }
259
as_ref(&self) -> BasePassRef<C>260 pub fn as_ref(&self) -> BasePassRef<C> {
261 BasePassRef {
262 label: self.label.as_deref(),
263 commands: &self.commands,
264 dynamic_offsets: &self.dynamic_offsets,
265 string_data: &self.string_data,
266 push_constant_data: &self.push_constant_data,
267 }
268 }
269 }
270
271 #[derive(Clone, Debug, Error)]
272 pub enum CommandEncoderError {
273 #[error("command encoder is invalid")]
274 Invalid,
275 #[error("command encoder must be active")]
276 NotRecording,
277 }
278
279 impl<G: GlobalIdentityHandlerFactory> Global<G> {
command_encoder_finish<A: HalApi>( &self, encoder_id: id::CommandEncoderId, _desc: &wgt::CommandBufferDescriptor<Label>, ) -> (id::CommandBufferId, Option<CommandEncoderError>)280 pub fn command_encoder_finish<A: HalApi>(
281 &self,
282 encoder_id: id::CommandEncoderId,
283 _desc: &wgt::CommandBufferDescriptor<Label>,
284 ) -> (id::CommandBufferId, Option<CommandEncoderError>) {
285 profiling::scope!("finish", "CommandEncoder");
286
287 let hub = A::hub(self);
288 let mut token = Token::root();
289 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
290
291 let error = match cmd_buf_guard.get_mut(encoder_id) {
292 Ok(cmd_buf) => match cmd_buf.status {
293 CommandEncoderStatus::Recording => {
294 cmd_buf.encoder.close();
295 cmd_buf.status = CommandEncoderStatus::Finished;
296 //Note: if we want to stop tracking the swapchain texture view,
297 // this is the place to do it.
298 log::trace!("Command buffer {:?} {:#?}", encoder_id, cmd_buf.trackers);
299 None
300 }
301 CommandEncoderStatus::Finished => Some(CommandEncoderError::NotRecording),
302 CommandEncoderStatus::Error => {
303 cmd_buf.encoder.discard();
304 Some(CommandEncoderError::Invalid)
305 }
306 },
307 Err(_) => Some(CommandEncoderError::Invalid),
308 };
309
310 (encoder_id, error)
311 }
312
command_encoder_push_debug_group<A: HalApi>( &self, encoder_id: id::CommandEncoderId, label: &str, ) -> Result<(), CommandEncoderError>313 pub fn command_encoder_push_debug_group<A: HalApi>(
314 &self,
315 encoder_id: id::CommandEncoderId,
316 label: &str,
317 ) -> Result<(), CommandEncoderError> {
318 profiling::scope!("push_debug_group", "CommandEncoder");
319
320 let hub = A::hub(self);
321 let mut token = Token::root();
322
323 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
324 let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id)?;
325
326 #[cfg(feature = "trace")]
327 if let Some(ref mut list) = cmd_buf.commands {
328 list.push(TraceCommand::PushDebugGroup(label.to_string()));
329 }
330
331 let cmd_buf_raw = cmd_buf.encoder.open();
332 unsafe {
333 cmd_buf_raw.begin_debug_marker(label);
334 }
335 Ok(())
336 }
337
command_encoder_insert_debug_marker<A: HalApi>( &self, encoder_id: id::CommandEncoderId, label: &str, ) -> Result<(), CommandEncoderError>338 pub fn command_encoder_insert_debug_marker<A: HalApi>(
339 &self,
340 encoder_id: id::CommandEncoderId,
341 label: &str,
342 ) -> Result<(), CommandEncoderError> {
343 profiling::scope!("insert_debug_marker", "CommandEncoder");
344
345 let hub = A::hub(self);
346 let mut token = Token::root();
347
348 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
349 let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id)?;
350
351 #[cfg(feature = "trace")]
352 if let Some(ref mut list) = cmd_buf.commands {
353 list.push(TraceCommand::InsertDebugMarker(label.to_string()));
354 }
355
356 let cmd_buf_raw = cmd_buf.encoder.open();
357 unsafe {
358 cmd_buf_raw.insert_debug_marker(label);
359 }
360 Ok(())
361 }
362
command_encoder_pop_debug_group<A: HalApi>( &self, encoder_id: id::CommandEncoderId, ) -> Result<(), CommandEncoderError>363 pub fn command_encoder_pop_debug_group<A: HalApi>(
364 &self,
365 encoder_id: id::CommandEncoderId,
366 ) -> Result<(), CommandEncoderError> {
367 profiling::scope!("pop_debug_marker", "CommandEncoder");
368
369 let hub = A::hub(self);
370 let mut token = Token::root();
371
372 let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
373 let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id)?;
374
375 #[cfg(feature = "trace")]
376 if let Some(ref mut list) = cmd_buf.commands {
377 list.push(TraceCommand::PopDebugGroup);
378 }
379
380 let cmd_buf_raw = cmd_buf.encoder.open();
381 unsafe {
382 cmd_buf_raw.end_debug_marker();
383 }
384 Ok(())
385 }
386 }
387
push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn) where PushFn: FnMut(u32, &[u32]),388 fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
389 where
390 PushFn: FnMut(u32, &[u32]),
391 {
392 let mut count_words = 0_u32;
393 let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
394 while count_words < size_words {
395 let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
396 let size_to_write_words =
397 (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
398
399 push_fn(
400 offset + count_bytes,
401 &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
402 );
403
404 count_words += size_to_write_words;
405 }
406 }
407
408 #[derive(Debug)]
409 struct StateChange<T> {
410 last_state: Option<T>,
411 }
412
413 impl<T: Copy + PartialEq> StateChange<T> {
new() -> Self414 fn new() -> Self {
415 Self { last_state: None }
416 }
set_and_check_redundant(&mut self, new_state: T) -> bool417 fn set_and_check_redundant(&mut self, new_state: T) -> bool {
418 let already_set = self.last_state == Some(new_state);
419 self.last_state = Some(new_state);
420 already_set
421 }
is_unset(&self) -> bool422 fn is_unset(&self) -> bool {
423 self.last_state.is_none()
424 }
reset(&mut self)425 fn reset(&mut self) {
426 self.last_state = None;
427 }
428 }
429
430 trait MapPassErr<T, O> {
map_pass_err(self, scope: PassErrorScope) -> Result<T, O>431 fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>;
432 }
433
434 #[derive(Clone, Copy, Debug, Error)]
435 pub enum PassErrorScope {
436 #[error("In a bundle parameter")]
437 Bundle,
438 #[error("In a pass parameter")]
439 Pass(id::CommandEncoderId),
440 #[error("In a set_bind_group command")]
441 SetBindGroup(id::BindGroupId),
442 #[error("In a set_pipeline command")]
443 SetPipelineRender(id::RenderPipelineId),
444 #[error("In a set_pipeline command")]
445 SetPipelineCompute(id::ComputePipelineId),
446 #[error("In a set_push_constant command")]
447 SetPushConstant,
448 #[error("In a set_vertex_buffer command")]
449 SetVertexBuffer(id::BufferId),
450 #[error("In a set_index_buffer command")]
451 SetIndexBuffer(id::BufferId),
452 #[error("In a set_viewport command")]
453 SetViewport,
454 #[error("In a set_scissor_rect command")]
455 SetScissorRect,
456 #[error("In a draw command, indexed:{indexed} indirect:{indirect}")]
457 Draw {
458 indexed: bool,
459 indirect: bool,
460 pipeline: Option<id::RenderPipelineId>,
461 },
462 #[error("While resetting queries after the renderpass was ran")]
463 QueryReset,
464 #[error("In a write_timestamp command")]
465 WriteTimestamp,
466 #[error("In a begin_pipeline_statistics_query command")]
467 BeginPipelineStatisticsQuery,
468 #[error("In a end_pipeline_statistics_query command")]
469 EndPipelineStatisticsQuery,
470 #[error("In a execute_bundle command")]
471 ExecuteBundle,
472 #[error("In a dispatch command, indirect:{indirect}")]
473 Dispatch {
474 indirect: bool,
475 pipeline: Option<id::ComputePipelineId>,
476 },
477 #[error("In a pop_debug_group command")]
478 PopDebugGroup,
479 }
480
481 impl PrettyError for PassErrorScope {
fmt_pretty(&self, fmt: &mut ErrorFormatter)482 fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
483 // This error is not in the error chain, only notes are needed
484 match *self {
485 Self::Pass(id) => {
486 fmt.command_buffer_label(&id);
487 }
488 Self::SetBindGroup(id) => {
489 fmt.bind_group_label(&id);
490 }
491 Self::SetPipelineRender(id) => {
492 fmt.render_pipeline_label(&id);
493 }
494 Self::SetPipelineCompute(id) => {
495 fmt.compute_pipeline_label(&id);
496 }
497 Self::SetVertexBuffer(id) => {
498 fmt.buffer_label(&id);
499 }
500 Self::SetIndexBuffer(id) => {
501 fmt.buffer_label(&id);
502 }
503 Self::Draw {
504 pipeline: Some(id), ..
505 } => {
506 fmt.render_pipeline_label(&id);
507 }
508 Self::Dispatch {
509 pipeline: Some(id), ..
510 } => {
511 fmt.compute_pipeline_label(&id);
512 }
513 _ => {}
514 }
515 }
516 }
517