wgpu_core/command/memory_init.rs
1use alloc::{
2 sync::Arc,
3 vec::{Drain, Vec},
4};
5use core::ops::Range;
6
7use hashbrown::hash_map::Entry;
8
9use crate::{
10 device::{Device, DeviceError},
11 init_tracker::*,
12 resource::{ParentDevice, RawResourceAccess, Texture, Trackable},
13 snatch::SnatchGuard,
14 track::{DeviceTracker, TextureTracker},
15 FastHashMap,
16};
17
18use super::{clear_texture, BakedCommands, ClearError};
19
20/// Surface that was discarded by `StoreOp::Discard` of a preceding renderpass.
21/// Any read access to this surface needs to be preceded by a texture initialization.
22#[derive(Clone)]
23pub(crate) struct TextureSurfaceDiscard {
24 pub texture: Arc<Texture>,
25 pub mip_level: u32,
26
27 /// For 3D textures, this field is a depth slice, otherwise, it is an array
28 /// layer. Unlike the primary initialization tracker, we _do_ track
29 /// individual discarded depth slices during encoding of a command buffer.
30 pub layer_or_depth_slice: u32,
31}
32
33pub(crate) type SurfacesInDiscardState = Vec<TextureSurfaceDiscard>;
34
35#[derive(Default)]
36pub(crate) struct CommandBufferTextureMemoryActions {
37 /// The tracker actions that we need to be executed before the command
38 /// buffer is executed.
39 init_actions: Vec<TextureInitTrackerAction>,
40 /// Tracks surfaces that were previously discarded within this command buffer.
41 ///
42 /// If a later pass reads from one of these surfaces, we must insert an immediate
43 /// clear operation in the command sequence. (Typically, memory initialization is done
44 /// in a dedicated pass prepended to the entire command buffer, but the discards we are
45 /// tracking here occur after that). Any discarded surfaces that are not reinitialized
46 /// prior to the end of the command buffer, will have their `initialization_status` set
47 /// to uninitialized at that point, except for depth slices, which must be reinitialized
48 /// prior to the end of the command buffer.
49 ///
50 /// We do a linear scan of the discarded surface list for _each_ initialization
51 /// action, i.e., we assume that most of the time there are no discarded surfaces.
52 /// If this list has more than a few items, performance will suffer.
53 discards: Vec<TextureSurfaceDiscard>,
54}
55
56impl CommandBufferTextureMemoryActions {
57 pub(crate) fn drain_init_actions(&mut self) -> Drain<'_, TextureInitTrackerAction> {
58 self.init_actions.drain(..)
59 }
60
61 pub(crate) fn discard(&mut self, discard: TextureSurfaceDiscard) {
62 self.discards.push(discard);
63 }
64
65 /// Registers a [`TextureInitTrackerAction`].
66 ///
67 /// Returns previously discarded surfaces that need to be initialized
68 /// *immediately*. Only returns a non-empty list if `action.kind` is
69 /// [`MemoryInitKind::NeedsInitializedMemory`]. These surfaces are removed
70 /// from the pending discard list, so the caller takes on the obligation to
71 /// clear them (via [`fixup_discarded_surfaces`]).
72 ///
73 /// `depth_slices` is the range of depth slices that `action` physically
74 /// accesses, or `None` if the access is to all slices of a 3D texture or
75 /// to some other texture type. The depth slice information does not flow to
76 /// the global init tracker, whose granularity is a whole mip level, but it
77 /// is important in deciding which pending discards have to be repaired
78 /// ahead of this action, as opposed to at the end of the command buffer.
79 #[must_use]
80 pub(crate) fn register_init_action(
81 &mut self,
82 action: &TextureInitTrackerAction,
83 depth_slices: Option<Range<u32>>,
84 ) -> SurfacesInDiscardState {
85 let is_3d = action.texture.desc.dimension == wgt::TextureDimension::D3;
86 debug_assert!(depth_slices.is_none() || is_3d);
87
88 // Texture subresources from `self.discards` that were discarded earlier in this
89 // command buffer and which the present `action` requires be initialized. These
90 // require inline initialization, which will be done by `fixup_discarded_surfaces`.
91 let mut immediately_necessary_clears = SurfacesInDiscardState::new();
92
93 // Note that within a command buffer we may stack arbitrary memory init
94 // actions on the same texture Since we react to them in sequence, they
95 // are going to be dropped again at queue submit
96 //
97 // We don't need to add MemoryInitKind::NeedsInitializedMemory to
98 // init_actions if a surface is part of the discard list. But that would
99 // mean splitting up the action which is more than we'd win here.
100 self.init_actions.extend(
101 action
102 .texture
103 .initialization_status
104 .read()
105 .check_action(action),
106 );
107
108 // We expect very few discarded surfaces at any point in time which is
109 // why a simple linear search is likely best. (i.e. most of the time
110 // self.discards is empty!)
111 let init_actions = &mut self.init_actions;
112 self.discards.retain(|discarded_surface| {
113 if !discarded_surface.texture.is_equal(&action.texture)
114 || !action
115 .range
116 .mip_range
117 .contains(&discarded_surface.mip_level)
118 {
119 return true;
120 }
121
122 let overlaps_discard = if is_3d {
123 // The `layer_range` for a `TextureInitTrackerAction` does not identify
124 // depth slices, so the caller passed that information separately.
125 depth_slices
126 .as_ref()
127 .is_none_or(|slices| slices.contains(&discarded_surface.layer_or_depth_slice))
128 } else {
129 action
130 .range
131 .layer_range
132 .contains(&discarded_surface.layer_or_depth_slice)
133 };
134 if !overlaps_discard {
135 return true;
136 }
137
138 if let MemoryInitKind::NeedsInitializedMemory = action.kind {
139 immediately_necessary_clears.push(discarded_surface.clone());
140
141 // Mark surface as implicitly initialized. This matters for non-3D textures
142 // where the discarded layer range may differ from the action layer range,
143 // and may have been uninitialized prior to discarding. For 3D textures,
144 // init state does not vary per layer, so we either emitted an init action
145 // above for the whole mip level, or it was already initialized and none
146 // is necessary.
147 if !is_3d {
148 let layer = discarded_surface.layer_or_depth_slice;
149 init_actions.push(TextureInitTrackerAction {
150 texture: discarded_surface.texture.clone(),
151 range: TextureInitRange {
152 mip_range: discarded_surface.mip_level
153 ..(discarded_surface.mip_level + 1),
154 layer_range: layer..(layer + 1),
155 },
156 kind: MemoryInitKind::ImplicitlyInitialized,
157 });
158 }
159 }
160 false
161 });
162
163 immediately_necessary_clears
164 }
165
166 // Shortcut for register_init_action when it is known that the action is an
167 // implicit init, not requiring any immediate resource init.
168 pub(crate) fn register_implicit_init(
169 &mut self,
170 texture: &Arc<Texture>,
171 range: TextureInitRange,
172 ) {
173 let must_be_empty = self.register_init_action(
174 &TextureInitTrackerAction {
175 texture: texture.clone(),
176 range,
177 kind: MemoryInitKind::ImplicitlyInitialized,
178 },
179 None,
180 );
181 assert!(must_be_empty.is_empty());
182 }
183}
184
185// Utility function that takes discarded surfaces from (several calls to)
186// register_init_action and initializes them on the spot.
187//
188// Takes care of barriers as well!
189pub(crate) fn fixup_discarded_surfaces<InitIter: Iterator<Item = TextureSurfaceDiscard>>(
190 inits: InitIter,
191 encoder: &mut dyn hal::DynCommandEncoder,
192 texture_tracker: &mut TextureTracker,
193 device: &Device,
194 snatch_guard: &SnatchGuard<'_>,
195) {
196 for init in inits {
197 let (layer_range, depth_slice) = if init.texture.desc.dimension == wgt::TextureDimension::D3
198 {
199 (0..1, Some(init.layer_or_depth_slice))
200 } else {
201 (
202 init.layer_or_depth_slice..(init.layer_or_depth_slice + 1),
203 None,
204 )
205 };
206 clear_texture(
207 &init.texture,
208 TextureInitRange {
209 mip_range: init.mip_level..(init.mip_level + 1),
210 layer_range,
211 },
212 depth_slice,
213 encoder,
214 texture_tracker,
215 &device.alignments,
216 device.zero_buffer.as_ref(),
217 snatch_guard,
218 device.instance_flags,
219 )
220 .unwrap();
221 }
222}
223
224impl BakedCommands {
225 /// Initialize buffers.
226 ///
227 /// Inserts all buffer initializations that are going to be needed for
228 /// executing the commands, and updates resource init states accordingly.
229 ///
230 /// The caller is responsible for checking that any buffer this may touch has not been
231 /// destroyed, and must have done that check under the same snatch guard that is passed
232 /// to this function.
233 ///
234 /// # Panics
235 /// If a destroyed buffer is encountered.
236 pub(crate) fn initialize_buffer_memory(
237 &mut self,
238 device_tracker: &mut DeviceTracker,
239 snatch_guard: &SnatchGuard<'_>,
240 ) {
241 profiling::scope!("initialize_buffer_memory");
242
243 // Gather init ranges for each buffer so we can collapse them.
244 // It is not possible to do this at an earlier point since previously
245 // executed command buffer change the resource init state.
246 let mut uninitialized_ranges_per_buffer = FastHashMap::default();
247 for buffer_use in self.buffer_memory_init_actions.drain(..) {
248 let mut initialization_status = buffer_use.buffer.initialization_status.write();
249
250 // align the end to 4
251 let end_remainder = buffer_use.range.end % wgt::COPY_BUFFER_ALIGNMENT;
252 let end = if end_remainder == 0 {
253 buffer_use.range.end
254 } else {
255 buffer_use.range.end + wgt::COPY_BUFFER_ALIGNMENT - end_remainder
256 };
257 let uninitialized_ranges = initialization_status.drain(buffer_use.range.start..end);
258
259 match buffer_use.kind {
260 MemoryInitKind::ImplicitlyInitialized => {}
261 MemoryInitKind::NeedsInitializedMemory => {
262 match uninitialized_ranges_per_buffer.entry(buffer_use.buffer.tracker_index()) {
263 Entry::Vacant(e) => {
264 e.insert((
265 buffer_use.buffer.clone(),
266 uninitialized_ranges.collect::<Vec<Range<wgt::BufferAddress>>>(),
267 ));
268 }
269 Entry::Occupied(mut e) => {
270 e.get_mut().1.extend(uninitialized_ranges);
271 }
272 }
273 }
274 }
275 }
276
277 for (buffer, mut ranges) in uninitialized_ranges_per_buffer.into_values() {
278 // Collapse touching ranges.
279 ranges.sort_by_key(|r| r.start);
280 for i in (1..ranges.len()).rev() {
281 // The memory init tracker made sure of this!
282 assert!(ranges[i - 1].end <= ranges[i].start);
283 if ranges[i].start == ranges[i - 1].end {
284 ranges[i - 1].end = ranges[i].end;
285 ranges.swap_remove(i); // Ordering not important at this point
286 }
287 }
288
289 // Don't do use_replace since the buffer may already no longer have
290 // a ref_count.
291 //
292 // However, we *know* that it is currently in use, so the tracker
293 // must already know about it.
294 let transition = device_tracker
295 .buffers
296 .set_single(&buffer, wgt::BufferUses::COPY_DST);
297
298 let raw_buf = buffer
299 .try_raw(snatch_guard)
300 .expect("attempt to initialize a destroyed buffer");
301
302 unsafe {
303 self.encoder.raw.transition_buffers(
304 transition
305 .map(|pending| pending.into_hal(&buffer, snatch_guard))
306 .as_slice(),
307 );
308 }
309
310 for range in ranges.iter() {
311 assert!(
312 range.start % wgt::COPY_BUFFER_ALIGNMENT == 0,
313 "Buffer {:?} has an uninitialized range with a start \
314 not aligned to 4 (start was {})",
315 raw_buf,
316 range.start
317 );
318 assert!(
319 range.end % wgt::COPY_BUFFER_ALIGNMENT == 0,
320 "Buffer {:?} has an uninitialized range with an end \
321 not aligned to 4 (end was {})",
322 raw_buf,
323 range.end
324 );
325
326 unsafe {
327 self.encoder.raw.clear_buffer(raw_buf, range.clone());
328 }
329 }
330 }
331 }
332
333 /// Initialize textures.
334 ///
335 /// Inserts all texture initializations that are going to be needed for
336 /// executing the commands, and updates resource init states accordingly. Any
337 /// non-3D textures that are left discarded by this command buffer will be marked as
338 /// uninitialized, and a list of any 3D depth slices is returned, to be reinitialized
339 /// just prior to the end of the command buffer.
340 ///
341 /// The caller is responsible for checking that any texture this may touch has not been
342 /// destroyed, and must have done that check under the same snatch guard that is passed
343 /// to this function.
344 ///
345 /// Note that any error returned from this function will become device loss in
346 /// [`crate::device::queue::Queue::submit`].
347 ///
348 /// # Panics
349 /// If a destroyed texture is encountered.
350 pub(crate) fn initialize_texture_memory(
351 &mut self,
352 device_tracker: &mut DeviceTracker,
353 device: &Device,
354 snatch_guard: &SnatchGuard<'_>,
355 ) -> Result<SurfacesInDiscardState, ClearError> {
356 profiling::scope!("initialize_texture_memory");
357
358 let mut depth_slice_discards = SurfacesInDiscardState::new();
359
360 let mut ranges: Vec<TextureInitRange> = Vec::new();
361 for texture_use in self.texture_memory_actions.drain_init_actions() {
362 {
363 let mut initialization_status = texture_use.texture.initialization_status.write();
364 let use_range = texture_use.range;
365 let affected_mip_trackers = initialization_status
366 .mips
367 .iter_mut()
368 .enumerate()
369 .skip(use_range.mip_range.start as usize)
370 .take((use_range.mip_range.end - use_range.mip_range.start) as usize);
371
372 match texture_use.kind {
373 MemoryInitKind::ImplicitlyInitialized => {
374 for (_, mip_tracker) in affected_mip_trackers {
375 mip_tracker.drain(use_range.layer_range.clone());
376 }
377 }
378 MemoryInitKind::NeedsInitializedMemory => {
379 for (mip_level, mip_tracker) in affected_mip_trackers {
380 for layer_range in mip_tracker.drain(use_range.layer_range.clone()) {
381 ranges.push(TextureInitRange {
382 mip_range: (mip_level as u32)..(mip_level as u32 + 1),
383 layer_range,
384 });
385 }
386 }
387 }
388 }
389 }
390
391 // TODO: Could we attempt some range collapsing here?
392 for range in ranges.drain(..) {
393 let clear_result = clear_texture(
394 &texture_use.texture,
395 range,
396 None,
397 self.encoder.raw.as_mut(),
398 &mut device_tracker.textures,
399 &device.alignments,
400 device.zero_buffer.as_ref(),
401 snatch_guard,
402 device.instance_flags,
403 );
404
405 // We panic on destroyed textures for symmetry with buffer
406 // initialization. It should not happen, but supposing it did,
407 // it would also be fine to return the error and lose the
408 // device in queue submit.
409 if matches!(clear_result, Err(ClearError::DestroyedResource(_))) {
410 panic!("attempt to initialize a destroyed texture");
411 } else {
412 clear_result?;
413 }
414 }
415 }
416
417 // Process any surfaces that remain in discarded state after
418 // the command buffer executes.
419 for surface_discard in self.texture_memory_actions.discards.drain(..) {
420 if surface_discard.texture.desc.dimension == wgt::TextureDimension::D3 {
421 // Depth slices are below the resolution of the init tracker, so
422 // collect a list of them to be initialized just prior to the
423 // end of the command buffer.
424 //
425 // We could optimize this by checking whether the entire mip is
426 // uninitialized (command buffer either did Clear+Discard when it was
427 // already uninitialized, or discarded every slice), and if so, ignore the
428 // pending discard, but it's not clear that happens enough for the
429 // optimization to be worth it.
430 depth_slice_discards.push(surface_discard);
431 } else {
432 // Anything else, record the discarded state in the initialization tracker.
433 surface_discard
434 .texture
435 .initialization_status
436 .write()
437 .discard(
438 surface_discard.mip_level,
439 surface_discard.layer_or_depth_slice,
440 );
441 }
442 }
443
444 Ok(depth_slice_discards)
445 }
446
447 /// Reinitialize any depth slices that were discarded during the command buffer and not
448 /// subsequently reinitialized.
449 ///
450 /// This is necessary because the initialization tracker does not track the status of
451 /// individual depth slices.
452 ///
453 /// We do not optimize the case where _every_ depth slice of a 3D texture is discarded.
454 pub(crate) fn initialize_discarded_depth_slices(
455 &mut self,
456 discards: SurfacesInDiscardState,
457 device_tracker: &mut DeviceTracker,
458 device: &Device,
459 snatch_guard: &SnatchGuard<'_>,
460 ) -> Result<(), ClearError> {
461 for discard in discards {
462 assert!(
463 discard.texture.desc.dimension == wgt::TextureDimension::D3,
464 "unexpected texture dimension {:?} in initialize_discarded_depth_slices",
465 discard.texture.desc.dimension,
466 );
467 let range = TextureInitRange {
468 mip_range: discard.mip_level..(discard.mip_level + 1),
469 layer_range: 0..1,
470 };
471 let clear_result = clear_texture(
472 &discard.texture,
473 range,
474 Some(discard.layer_or_depth_slice),
475 self.encoder.raw.as_mut(),
476 &mut device_tracker.textures,
477 &device.alignments,
478 device.zero_buffer.as_ref(),
479 snatch_guard,
480 device.instance_flags,
481 );
482 // We panic on destroyed textures for symmetry with the main buffer
483 // and initialization pass. It should not happen, but supposing it
484 // did, it would also be fine to return the error and lose the
485 // device in queue submit.
486 if matches!(clear_result, Err(ClearError::DestroyedResource(_))) {
487 panic!("attempt to initialize a destroyed texture");
488 } else {
489 clear_result?;
490 }
491 }
492
493 Ok(())
494 }
495
496 pub(crate) fn process_deferred_query_set_resolves(
497 &mut self,
498 device: &Device,
499 snatch_guard: &SnatchGuard<'_>,
500 ) -> Result<(), DeviceError> {
501 profiling::scope!("process_deferred_query_set_resolves");
502
503 for mut resolve in self.deferred_query_set_resolves.drain(..).rev() {
504 let raw_dst = resolve.dst_buffer.try_raw(snatch_guard).unwrap();
505 let raw_query_set = resolve.query_set.try_raw(snatch_guard).unwrap();
506
507 let raw_encoder = self.encoder.open_pass(crate::hal_label(
508 Some("(wgpu internal) Deferred query set resolve"),
509 device.instance_flags,
510 ))?;
511
512 let initialized_slots_guard = resolve.query_set.initialized_slots.lock();
513 let initialized_slots =
514 if let Some(query_set_writes) = resolve.query_set_writes.as_mut() {
515 query_set_writes.or(&initialized_slots_guard);
516 &*query_set_writes
517 } else {
518 &*initialized_slots_guard
519 };
520
521 let mut start = resolve.start_query;
522 while start < resolve.end_query {
523 let is_initialized = initialized_slots[start as usize];
524 let end = (start + 1..resolve.end_query)
525 .find(|&i| initialized_slots[i as usize] != is_initialized)
526 .unwrap_or(resolve.end_query);
527
528 let byte_offset = resolve.destination_offset
529 + (start - resolve.start_query) as u64 * resolve.stride;
530 let byte_len = (end - start) as u64 * resolve.stride;
531
532 if is_initialized {
533 unsafe {
534 raw_encoder.copy_query_results(
535 raw_query_set,
536 start..end,
537 raw_dst,
538 byte_offset,
539 wgt::BufferSize::new_unchecked(resolve.stride),
540 );
541 }
542 } else {
543 unsafe {
544 raw_encoder.clear_buffer(raw_dst, byte_offset..byte_offset + byte_len);
545 }
546 }
547
548 start = end;
549 }
550 drop(initialized_slots_guard);
551
552 self.encoder.close_and_insert_at(resolve.insertion_point)?;
553 }
554
555 // Update query set initialization state.
556 for query_set in self.trackers.query_sets.used_resources() {
557 if let Some(slots) = self.query_set_writes.get(&query_set.tracker_index()) {
558 let mut initialized = query_set.initialized_slots.lock();
559 initialized.or(slots);
560 }
561 }
562
563 Ok(())
564 }
565}