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    pub layer: u32,
27}
28
29pub(crate) type SurfacesInDiscardState = Vec<TextureSurfaceDiscard>;
30
31#[derive(Default)]
32pub(crate) struct CommandBufferTextureMemoryActions {
33    /// The tracker actions that we need to be executed before the command
34    /// buffer is executed.
35    init_actions: Vec<TextureInitTrackerAction>,
36    /// All the discards that haven't been followed by init again within the
37    /// command buffer i.e. everything in this list resets the texture init
38    /// state *after* the command buffer execution
39    discards: Vec<TextureSurfaceDiscard>,
40}
41
42impl CommandBufferTextureMemoryActions {
43    pub(crate) fn drain_init_actions(&mut self) -> Drain<'_, TextureInitTrackerAction> {
44        self.init_actions.drain(..)
45    }
46
47    pub(crate) fn discard(&mut self, discard: TextureSurfaceDiscard) {
48        self.discards.push(discard);
49    }
50
51    // Registers a TextureInitTrackerAction.
52    // Returns previously discarded surface that need to be initialized *immediately* now.
53    // Only returns a non-empty list if action is MemoryInitKind::NeedsInitializedMemory.
54    #[must_use]
55    pub(crate) fn register_init_action(
56        &mut self,
57        action: &TextureInitTrackerAction,
58    ) -> SurfacesInDiscardState {
59        let mut immediately_necessary_clears = SurfacesInDiscardState::new();
60
61        // Note that within a command buffer we may stack arbitrary memory init
62        // actions on the same texture Since we react to them in sequence, they
63        // are going to be dropped again at queue submit
64        //
65        // We don't need to add MemoryInitKind::NeedsInitializedMemory to
66        // init_actions if a surface is part of the discard list. But that would
67        // mean splitting up the action which is more than we'd win here.
68        self.init_actions.extend(
69            action
70                .texture
71                .initialization_status
72                .read()
73                .check_action(action),
74        );
75
76        // We expect very few discarded surfaces at any point in time which is
77        // why a simple linear search is likely best. (i.e. most of the time
78        // self.discards is empty!)
79        let init_actions = &mut self.init_actions;
80        self.discards.retain(|discarded_surface| {
81            if discarded_surface.texture.is_equal(&action.texture)
82                && action.range.layer_range.contains(&discarded_surface.layer)
83                && action
84                    .range
85                    .mip_range
86                    .contains(&discarded_surface.mip_level)
87            {
88                if let MemoryInitKind::NeedsInitializedMemory = action.kind {
89                    immediately_necessary_clears.push(discarded_surface.clone());
90
91                    // Mark surface as implicitly initialized (this is relevant
92                    // because it might have been uninitialized prior to
93                    // discarding
94                    init_actions.push(TextureInitTrackerAction {
95                        texture: discarded_surface.texture.clone(),
96                        range: TextureInitRange {
97                            mip_range: discarded_surface.mip_level
98                                ..(discarded_surface.mip_level + 1),
99                            layer_range: discarded_surface.layer..(discarded_surface.layer + 1),
100                        },
101                        kind: MemoryInitKind::ImplicitlyInitialized,
102                    });
103                }
104                false
105            } else {
106                true
107            }
108        });
109
110        immediately_necessary_clears
111    }
112
113    // Shortcut for register_init_action when it is known that the action is an
114    // implicit init, not requiring any immediate resource init.
115    pub(crate) fn register_implicit_init(
116        &mut self,
117        texture: &Arc<Texture>,
118        range: TextureInitRange,
119    ) {
120        let must_be_empty = self.register_init_action(&TextureInitTrackerAction {
121            texture: texture.clone(),
122            range,
123            kind: MemoryInitKind::ImplicitlyInitialized,
124        });
125        assert!(must_be_empty.is_empty());
126    }
127}
128
129// Utility function that takes discarded surfaces from (several calls to)
130// register_init_action and initializes them on the spot.
131//
132// Takes care of barriers as well!
133pub(crate) fn fixup_discarded_surfaces<InitIter: Iterator<Item = TextureSurfaceDiscard>>(
134    inits: InitIter,
135    encoder: &mut dyn hal::DynCommandEncoder,
136    texture_tracker: &mut TextureTracker,
137    device: &Device,
138    snatch_guard: &SnatchGuard<'_>,
139) {
140    for init in inits {
141        clear_texture(
142            &init.texture,
143            TextureInitRange {
144                mip_range: init.mip_level..(init.mip_level + 1),
145                layer_range: init.layer..(init.layer + 1),
146            },
147            encoder,
148            texture_tracker,
149            &device.alignments,
150            device.zero_buffer.as_ref(),
151            snatch_guard,
152            device.instance_flags,
153        )
154        .unwrap();
155    }
156}
157
158impl BakedCommands {
159    /// Initialize buffers.
160    ///
161    /// Inserts all buffer initializations that are going to be needed for
162    /// executing the commands, and updates resource init states accordingly.
163    ///
164    /// The caller is responsible for checking that any buffer this may touch has not been
165    /// destroyed, and must have done that check under the same snatch guard that is passed
166    /// to this function.
167    ///
168    /// # Panics
169    /// If a destroyed buffer is encountered.
170    pub(crate) fn initialize_buffer_memory(
171        &mut self,
172        device_tracker: &mut DeviceTracker,
173        snatch_guard: &SnatchGuard<'_>,
174    ) {
175        profiling::scope!("initialize_buffer_memory");
176
177        // Gather init ranges for each buffer so we can collapse them.
178        // It is not possible to do this at an earlier point since previously
179        // executed command buffer change the resource init state.
180        let mut uninitialized_ranges_per_buffer = FastHashMap::default();
181        for buffer_use in self.buffer_memory_init_actions.drain(..) {
182            let mut initialization_status = buffer_use.buffer.initialization_status.write();
183
184            // align the end to 4
185            let end_remainder = buffer_use.range.end % wgt::COPY_BUFFER_ALIGNMENT;
186            let end = if end_remainder == 0 {
187                buffer_use.range.end
188            } else {
189                buffer_use.range.end + wgt::COPY_BUFFER_ALIGNMENT - end_remainder
190            };
191            let uninitialized_ranges = initialization_status.drain(buffer_use.range.start..end);
192
193            match buffer_use.kind {
194                MemoryInitKind::ImplicitlyInitialized => {}
195                MemoryInitKind::NeedsInitializedMemory => {
196                    match uninitialized_ranges_per_buffer.entry(buffer_use.buffer.tracker_index()) {
197                        Entry::Vacant(e) => {
198                            e.insert((
199                                buffer_use.buffer.clone(),
200                                uninitialized_ranges.collect::<Vec<Range<wgt::BufferAddress>>>(),
201                            ));
202                        }
203                        Entry::Occupied(mut e) => {
204                            e.get_mut().1.extend(uninitialized_ranges);
205                        }
206                    }
207                }
208            }
209        }
210
211        for (buffer, mut ranges) in uninitialized_ranges_per_buffer.into_values() {
212            // Collapse touching ranges.
213            ranges.sort_by_key(|r| r.start);
214            for i in (1..ranges.len()).rev() {
215                // The memory init tracker made sure of this!
216                assert!(ranges[i - 1].end <= ranges[i].start);
217                if ranges[i].start == ranges[i - 1].end {
218                    ranges[i - 1].end = ranges[i].end;
219                    ranges.swap_remove(i); // Ordering not important at this point
220                }
221            }
222
223            // Don't do use_replace since the buffer may already no longer have
224            // a ref_count.
225            //
226            // However, we *know* that it is currently in use, so the tracker
227            // must already know about it.
228            let transition = device_tracker
229                .buffers
230                .set_single(&buffer, wgt::BufferUses::COPY_DST);
231
232            let raw_buf = buffer
233                .try_raw(snatch_guard)
234                .expect("attempt to initialize a destroyed buffer");
235
236            unsafe {
237                self.encoder.raw.transition_buffers(
238                    transition
239                        .map(|pending| pending.into_hal(&buffer, snatch_guard))
240                        .as_slice(),
241                );
242            }
243
244            for range in ranges.iter() {
245                assert!(
246                    range.start % wgt::COPY_BUFFER_ALIGNMENT == 0,
247                    "Buffer {:?} has an uninitialized range with a start \
248                         not aligned to 4 (start was {})",
249                    raw_buf,
250                    range.start
251                );
252                assert!(
253                    range.end % wgt::COPY_BUFFER_ALIGNMENT == 0,
254                    "Buffer {:?} has an uninitialized range with an end \
255                         not aligned to 4 (end was {})",
256                    raw_buf,
257                    range.end
258                );
259
260                unsafe {
261                    self.encoder.raw.clear_buffer(raw_buf, range.clone());
262                }
263            }
264        }
265    }
266
267    /// Initialize textures.
268    ///
269    /// Inserts all texture initializations that are going to be needed for
270    /// executing the commands, and updates resource init states accordingly. Any
271    /// textures that are left discarded by this command buffer will be marked as
272    /// uninitialized.
273    ///
274    /// The caller is responsible for checking that any texture this may touch has not been
275    /// destroyed, and must have done that check under the same snatch guard that is passed
276    /// to this function.
277    ///
278    /// Note that any error returned from this function will become device loss in
279    /// [`crate::device::queue::Queue::submit`].
280    ///
281    /// # Panics
282    /// If a destroyed texture is encountered.
283    pub(crate) fn initialize_texture_memory(
284        &mut self,
285        device_tracker: &mut DeviceTracker,
286        device: &Device,
287        snatch_guard: &SnatchGuard<'_>,
288    ) -> Result<(), ClearError> {
289        profiling::scope!("initialize_texture_memory");
290
291        let mut ranges: Vec<TextureInitRange> = Vec::new();
292        for texture_use in self.texture_memory_actions.drain_init_actions() {
293            let mut initialization_status = texture_use.texture.initialization_status.write();
294            let use_range = texture_use.range;
295            let affected_mip_trackers = initialization_status
296                .mips
297                .iter_mut()
298                .enumerate()
299                .skip(use_range.mip_range.start as usize)
300                .take((use_range.mip_range.end - use_range.mip_range.start) as usize);
301
302            match texture_use.kind {
303                MemoryInitKind::ImplicitlyInitialized => {
304                    for (_, mip_tracker) in affected_mip_trackers {
305                        mip_tracker.drain(use_range.layer_range.clone());
306                    }
307                }
308                MemoryInitKind::NeedsInitializedMemory => {
309                    for (mip_level, mip_tracker) in affected_mip_trackers {
310                        for layer_range in mip_tracker.drain(use_range.layer_range.clone()) {
311                            ranges.push(TextureInitRange {
312                                mip_range: (mip_level as u32)..(mip_level as u32 + 1),
313                                layer_range,
314                            });
315                        }
316                    }
317                }
318            }
319
320            // TODO: Could we attempt some range collapsing here?
321            for range in ranges.drain(..) {
322                let clear_result = clear_texture(
323                    &texture_use.texture,
324                    range,
325                    self.encoder.raw.as_mut(),
326                    &mut device_tracker.textures,
327                    &device.alignments,
328                    device.zero_buffer.as_ref(),
329                    snatch_guard,
330                    device.instance_flags,
331                );
332
333                // We panic on destroyed textures for symmetry with buffer
334                // initialization. It should not happen, but supposing it did,
335                // it would also be fine to return the error and lose the
336                // device in queue submit.
337                if matches!(clear_result, Err(ClearError::DestroyedResource(_))) {
338                    panic!("attempt to initialize a destroyed texture");
339                } else {
340                    clear_result?;
341                }
342            }
343        }
344
345        // Now that all buffers/textures have the proper init state for before
346        // cmdbuf start, we discard init states for textures it left discarded
347        // after its execution.
348        for surface_discard in self.texture_memory_actions.discards.iter() {
349            surface_discard
350                .texture
351                .initialization_status
352                .write()
353                .discard(surface_discard.mip_level, surface_discard.layer);
354        }
355
356        Ok(())
357    }
358
359    pub(crate) fn process_deferred_query_set_resolves(
360        &mut self,
361        device: &Device,
362        snatch_guard: &SnatchGuard<'_>,
363    ) -> Result<(), DeviceError> {
364        profiling::scope!("process_deferred_query_set_resolves");
365
366        for mut resolve in self.deferred_query_set_resolves.drain(..).rev() {
367            let raw_dst = resolve.dst_buffer.try_raw(snatch_guard).unwrap();
368            let raw_query_set = resolve.query_set.try_raw(snatch_guard).unwrap();
369
370            let raw_encoder = self.encoder.open_pass(crate::hal_label(
371                Some("(wgpu internal) Deferred query set resolve"),
372                device.instance_flags,
373            ))?;
374
375            let initialized_slots_guard = resolve.query_set.initialized_slots.lock();
376            let initialized_slots =
377                if let Some(query_set_writes) = resolve.query_set_writes.as_mut() {
378                    query_set_writes.or(&initialized_slots_guard);
379                    &*query_set_writes
380                } else {
381                    &*initialized_slots_guard
382                };
383
384            let mut start = resolve.start_query;
385            while start < resolve.end_query {
386                let is_initialized = initialized_slots[start as usize];
387                let end = (start + 1..resolve.end_query)
388                    .find(|&i| initialized_slots[i as usize] != is_initialized)
389                    .unwrap_or(resolve.end_query);
390
391                let byte_offset = resolve.destination_offset
392                    + (start - resolve.start_query) as u64 * resolve.stride;
393                let byte_len = (end - start) as u64 * resolve.stride;
394
395                if is_initialized {
396                    unsafe {
397                        raw_encoder.copy_query_results(
398                            raw_query_set,
399                            start..end,
400                            raw_dst,
401                            byte_offset,
402                            wgt::BufferSize::new_unchecked(resolve.stride),
403                        );
404                    }
405                } else {
406                    unsafe {
407                        raw_encoder.clear_buffer(raw_dst, byte_offset..byte_offset + byte_len);
408                    }
409                }
410
411                start = end;
412            }
413            drop(initialized_slots_guard);
414
415            self.encoder.close_and_insert_at(resolve.insertion_point)?;
416        }
417
418        // Update query set initialization state.
419        for query_set in self.trackers.query_sets.used_resources() {
420            if let Some(slots) = self.query_set_writes.get(&query_set.tracker_index()) {
421                let mut initialized = query_set.initialized_slots.lock();
422                initialized.or(slots);
423            }
424        }
425
426        Ok(())
427    }
428}