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}