wgpu_core/init_tracker/
mod.rs

1/*! Lazy initialization of texture and buffer memory.
2
3The WebGPU specification requires all texture & buffer memory to be
4zero initialized on first read. To avoid unnecessary inits, we track
5the initialization status of every resource and perform inits lazily.
6
7The granularity is different for buffers and textures:
8
9- Buffer: Byte granularity to support usecases with large, partially
10  bound buffers well.
11
12- Texture: Mip-level per layer. That is, a 2D surface is either
13  completely initialized or not, subrects are not tracked.
14
15Every use of a buffer/texture generates a InitTrackerAction which are
16recorded and later resolved at queue submit by merging them with the
17current state and each other in execution order.
18
19It is important to note that from the point of view of the memory init
20system there are two kind of writes:
21
22- **Full writes**: Any kind of memcpy operation. These cause a
23  `MemoryInitKind.ImplicitlyInitialized` action.
24
25- **(Potentially) partial writes**: For example, write use in a
26  Shader. The system is not able to determine if a resource is fully
27  initialized afterwards but is no longer allowed to perform any
28  clears, therefore this leads to a
29  `MemoryInitKind.NeedsInitializedMemory` action, exactly like a read
30  would.
31
32 */
33
34use core::{fmt, iter, ops::Range};
35
36use smallvec::SmallVec;
37
38mod buffer;
39mod texture;
40
41pub(crate) use buffer::{BufferInitTracker, BufferInitTrackerAction};
42pub(crate) use texture::{
43    has_copy_partial_init_tracker_coverage, TextureInitRange, TextureInitTracker,
44    TextureInitTrackerAction,
45};
46
47#[derive(Debug, Clone, Copy)]
48pub(crate) enum MemoryInitKind {
49    // The memory range is going to be written by an already initialized source,
50    // thus doesn't need extra attention other than marking as initialized.
51    ImplicitlyInitialized,
52    // The memory range is going to be read, therefore needs to ensure prior
53    // initialization.
54    NeedsInitializedMemory,
55}
56
57// Most of the time a resource is either fully uninitialized (one element) or
58// initialized (zero elements).
59type UninitializedRangeVec<Idx> = SmallVec<[Range<Idx>; 1]>;
60
61/// Tracks initialization status of a linear range from 0..size
62#[derive(Debug, Clone)]
63pub(crate) struct InitTracker<Idx: Ord + Copy + Default> {
64    /// Non-overlapping list of all uninitialized ranges, sorted by
65    /// range end.
66    uninitialized_ranges: UninitializedRangeVec<Idx>,
67}
68
69pub(crate) struct UninitializedIter<'a, Idx: fmt::Debug + Ord + Copy> {
70    uninitialized_ranges: &'a UninitializedRangeVec<Idx>,
71    drain_range: Range<Idx>,
72    next_index: usize,
73}
74
75impl<'a, Idx> Iterator for UninitializedIter<'a, Idx>
76where
77    Idx: fmt::Debug + Ord + Copy,
78{
79    type Item = Range<Idx>;
80
81    fn next(&mut self) -> Option<Self::Item> {
82        self.uninitialized_ranges
83            .get(self.next_index)
84            .and_then(|range| {
85                if range.start < self.drain_range.end {
86                    self.next_index += 1;
87                    Some(
88                        range.start.max(self.drain_range.start)
89                            ..range.end.min(self.drain_range.end),
90                    )
91                } else {
92                    None
93                }
94            })
95    }
96}
97
98pub(crate) struct InitTrackerDrain<'a, Idx: fmt::Debug + Ord + Copy> {
99    uninitialized_ranges: &'a mut UninitializedRangeVec<Idx>,
100    drain_range: Range<Idx>,
101    first_index: usize,
102    next_index: usize,
103}
104
105impl<'a, Idx> Iterator for InitTrackerDrain<'a, Idx>
106where
107    Idx: fmt::Debug + Ord + Copy,
108{
109    type Item = Range<Idx>;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        if let Some(r) = self
113            .uninitialized_ranges
114            .get(self.next_index)
115            .and_then(|range| {
116                if range.start < self.drain_range.end {
117                    Some(range.clone())
118                } else {
119                    None
120                }
121            })
122        {
123            self.next_index += 1;
124            Some(r.start.max(self.drain_range.start)..r.end.min(self.drain_range.end))
125        } else {
126            let num_affected = self.next_index - self.first_index;
127            if num_affected == 0 {
128                return None;
129            }
130            let first_range = &mut self.uninitialized_ranges[self.first_index];
131
132            // Split one "big" uninitialized range?
133            if num_affected == 1
134                && first_range.start < self.drain_range.start
135                && first_range.end > self.drain_range.end
136            {
137                let old_start = first_range.start;
138                first_range.start = self.drain_range.end;
139                self.uninitialized_ranges
140                    .insert(self.first_index, old_start..self.drain_range.start);
141            }
142            // Adjust border ranges and delete everything in-between.
143            else {
144                let remove_start = if first_range.start >= self.drain_range.start {
145                    self.first_index
146                } else {
147                    first_range.end = self.drain_range.start;
148                    self.first_index + 1
149                };
150
151                let last_range = &mut self.uninitialized_ranges[self.next_index - 1];
152                let remove_end = if last_range.end <= self.drain_range.end {
153                    self.next_index
154                } else {
155                    last_range.start = self.drain_range.end;
156                    self.next_index - 1
157                };
158
159                self.uninitialized_ranges.drain(remove_start..remove_end);
160            }
161
162            None
163        }
164    }
165}
166
167impl<'a, Idx> Drop for InitTrackerDrain<'a, Idx>
168where
169    Idx: fmt::Debug + Ord + Copy,
170{
171    fn drop(&mut self) {
172        if self.next_index <= self.first_index {
173            for _ in self {}
174        }
175    }
176}
177
178impl<Idx> InitTracker<Idx>
179where
180    Idx: fmt::Debug + Ord + Copy + Default,
181{
182    pub(crate) fn new(size: Idx) -> Self {
183        Self {
184            uninitialized_ranges: iter::once(Idx::default()..size).collect(),
185        }
186    }
187
188    /// Checks for uninitialized ranges within a given query range.
189    ///
190    /// If `query_range` includes any uninitialized portions of this init
191    /// tracker's resource, return the smallest subrange of `query_range` that
192    /// covers all uninitialized regions.
193    ///
194    /// The returned range may be larger than necessary, to keep this function
195    /// O(log n).
196    pub(crate) fn check(&self, query_range: Range<Idx>) -> Option<Range<Idx>> {
197        let index = self
198            .uninitialized_ranges
199            .partition_point(|r| r.end <= query_range.start);
200        self.uninitialized_ranges
201            .get(index)
202            .and_then(|start_range| {
203                if start_range.start < query_range.end {
204                    let start = start_range.start.max(query_range.start);
205                    match self.uninitialized_ranges.get(index + 1) {
206                        Some(next_range) => {
207                            if next_range.start < query_range.end {
208                                // Would need to keep iterating for more
209                                // accurate upper bound. Don't do that here.
210                                Some(start..query_range.end)
211                            } else {
212                                Some(start..start_range.end.min(query_range.end))
213                            }
214                        }
215                        None => Some(start..start_range.end.min(query_range.end)),
216                    }
217                } else {
218                    None
219                }
220            })
221    }
222
223    // Returns an iterator over the uninitialized ranges in a query range.
224    pub(crate) fn uninitialized(&mut self, drain_range: Range<Idx>) -> UninitializedIter<'_, Idx> {
225        let index = self
226            .uninitialized_ranges
227            .partition_point(|r| r.end <= drain_range.start);
228        UninitializedIter {
229            drain_range,
230            uninitialized_ranges: &self.uninitialized_ranges,
231            next_index: index,
232        }
233    }
234
235    // Drains uninitialized ranges in a query range.
236    pub(crate) fn drain(&mut self, drain_range: Range<Idx>) -> InitTrackerDrain<'_, Idx> {
237        if drain_range.is_empty() {
238            let len = self.uninitialized_ranges.len();
239            InitTrackerDrain {
240                drain_range,
241                uninitialized_ranges: &mut self.uninitialized_ranges,
242                first_index: len,
243                next_index: len,
244            }
245        } else {
246            let index = self
247                .uninitialized_ranges
248                .partition_point(|r| r.end <= drain_range.start);
249            InitTrackerDrain {
250                drain_range,
251                uninitialized_ranges: &mut self.uninitialized_ranges,
252                first_index: index,
253                next_index: index,
254            }
255        }
256    }
257}
258
259impl InitTracker<u32> {
260    // Makes a single entry uninitialized if not already uninitialized
261    pub(crate) fn discard(&mut self, pos: u32) {
262        // first range where end>=idx
263        let r_idx = self.uninitialized_ranges.partition_point(|r| r.end < pos);
264        if let Some(r) = self.uninitialized_ranges.get(r_idx) {
265            // Extend range at end
266            if r.end == pos {
267                // merge with next?
268                if let Some(right) = self.uninitialized_ranges.get(r_idx + 1) {
269                    if right.start == pos + 1 {
270                        self.uninitialized_ranges[r_idx] = r.start..right.end;
271                        self.uninitialized_ranges.remove(r_idx + 1);
272                        return;
273                    }
274                }
275                self.uninitialized_ranges[r_idx] = r.start..(pos + 1);
276            } else if r.start > pos {
277                // may still extend range at beginning
278                if r.start == pos + 1 {
279                    self.uninitialized_ranges[r_idx] = pos..r.end;
280                } else {
281                    // previous range end must be smaller than idx, therefore no merge possible
282                    self.uninitialized_ranges.insert(r_idx, pos..(pos + 1));
283                }
284            }
285        } else {
286            self.uninitialized_ranges.push(pos..(pos + 1));
287        }
288    }
289}
290
291#[cfg(test)]
292mod test {
293    use alloc::{vec, vec::Vec};
294    use core::ops::Range;
295
296    type Tracker = super::InitTracker<u32>;
297
298    #[test]
299    fn check_for_newly_created_tracker() {
300        let tracker = Tracker::new(10);
301        assert_eq!(tracker.check(0..10), Some(0..10));
302        assert_eq!(tracker.check(0..3), Some(0..3));
303        assert_eq!(tracker.check(3..4), Some(3..4));
304        assert_eq!(tracker.check(4..10), Some(4..10));
305    }
306
307    #[test]
308    fn check_for_drained_tracker() {
309        let mut tracker = Tracker::new(10);
310        tracker.drain(0..10);
311        assert_eq!(tracker.check(0..10), None);
312        assert_eq!(tracker.check(0..3), None);
313        assert_eq!(tracker.check(3..4), None);
314        assert_eq!(tracker.check(4..10), None);
315    }
316
317    #[test]
318    fn check_for_partially_filled_tracker() {
319        let mut tracker = Tracker::new(25);
320        // Two regions of uninitialized memory
321        tracker.drain(0..5);
322        tracker.drain(10..15);
323        tracker.drain(20..25);
324
325        assert_eq!(tracker.check(0..25), Some(5..25)); // entire range
326
327        assert_eq!(tracker.check(0..5), None); // left non-overlapping
328        assert_eq!(tracker.check(3..8), Some(5..8)); // left overlapping region
329        assert_eq!(tracker.check(3..17), Some(5..17)); // left overlapping region + contained region
330
331        // right overlapping region + contained region (yes, doesn't fix range end!)
332        assert_eq!(tracker.check(8..22), Some(8..22));
333        // right overlapping region
334        assert_eq!(tracker.check(17..22), Some(17..20));
335        // right non-overlapping
336        assert_eq!(tracker.check(20..25), None);
337    }
338
339    #[test]
340    fn drain_already_drained() {
341        let mut tracker = Tracker::new(30);
342        tracker.drain(10..20);
343
344        // Overlapping with non-cleared
345        tracker.drain(5..15); // Left overlap
346        tracker.drain(15..25); // Right overlap
347        tracker.drain(0..30); // Inner overlap
348
349        // Clear fully cleared
350        tracker.drain(0..30);
351
352        assert_eq!(tracker.check(0..30), None);
353    }
354
355    #[test]
356    fn drain_never_returns_ranges_twice_for_same_range() {
357        let mut tracker = Tracker::new(19);
358        assert_eq!(tracker.drain(0..19).count(), 1);
359        assert_eq!(tracker.drain(0..19).count(), 0);
360
361        let mut tracker = Tracker::new(17);
362        assert_eq!(tracker.drain(5..8).count(), 1);
363        assert_eq!(tracker.drain(5..8).count(), 0);
364        assert_eq!(tracker.drain(1..3).count(), 1);
365        assert_eq!(tracker.drain(1..3).count(), 0);
366        assert_eq!(tracker.drain(7..13).count(), 1);
367        assert_eq!(tracker.drain(7..13).count(), 0);
368    }
369
370    #[test]
371    fn drain_splits_ranges_correctly() {
372        let mut tracker = Tracker::new(1337);
373        assert_eq!(
374            tracker.drain(21..42).collect::<Vec<Range<u32>>>(),
375            vec![21..42]
376        );
377        assert_eq!(
378            tracker.drain(900..1000).collect::<Vec<Range<u32>>>(),
379            vec![900..1000]
380        );
381
382        // Split ranges.
383        assert_eq!(
384            tracker.drain(5..1003).collect::<Vec<Range<u32>>>(),
385            vec![5..21, 42..900, 1000..1003]
386        );
387        assert_eq!(
388            tracker.drain(0..1337).collect::<Vec<Range<u32>>>(),
389            vec![0..5, 1003..1337]
390        );
391
392        // Doesn't split for an empty query
393        let mut tracker = Tracker::new(2);
394        assert_eq!(tracker.drain(1..1).count(), 0, "{tracker:?}");
395        assert_eq!(tracker.uninitialized_ranges.len(), 1, "{tracker:?}");
396        assert_eq!(tracker.uninitialized_ranges[0], 0..2, "{tracker:?}");
397
398        // If the range were split to `[0..1, 1..2]` before, this discard
399        // would result in `[0..2, 1..2]`.
400        tracker.discard(1);
401        assert_eq!(tracker.uninitialized_ranges.len(), 1, "{tracker:?}");
402        assert_eq!(tracker.uninitialized_ranges[0], 0..2, "{tracker:?}");
403    }
404
405    #[test]
406    fn discard_adds_range_on_cleared() {
407        let mut tracker = Tracker::new(10);
408        tracker.drain(0..10);
409        tracker.discard(0);
410        tracker.discard(5);
411        tracker.discard(9);
412        assert_eq!(tracker.check(0..1), Some(0..1), "{tracker:?}");
413        assert_eq!(tracker.check(1..5), None, "{tracker:?}");
414        assert_eq!(tracker.check(5..6), Some(5..6), "{tracker:?}");
415        assert_eq!(tracker.check(6..9), None, "{tracker:?}");
416        assert_eq!(tracker.check(9..10), Some(9..10), "{tracker:?}");
417
418        let mut tracker = Tracker::new(10);
419        tracker.drain(0..10);
420        tracker.discard(9);
421        tracker.discard(5);
422        tracker.discard(0);
423        assert_eq!(tracker.check(0..1), Some(0..1), "{tracker:?}");
424        assert_eq!(tracker.check(1..5), None, "{tracker:?}");
425        assert_eq!(tracker.check(5..6), Some(5..6), "{tracker:?}");
426        assert_eq!(tracker.check(6..9), None, "{tracker:?}");
427        assert_eq!(tracker.check(9..10), Some(9..10), "{tracker:?}");
428    }
429
430    #[test]
431    fn discard_does_nothing_on_uncleared() {
432        let mut tracker = Tracker::new(10);
433        tracker.discard(0);
434        tracker.discard(5);
435        tracker.discard(9);
436        assert_eq!(tracker.uninitialized_ranges.len(), 1);
437        assert_eq!(tracker.uninitialized_ranges[0], 0..10);
438    }
439
440    #[test]
441    fn discard_extends_ranges() {
442        let mut tracker = Tracker::new(10);
443        tracker.drain(3..7);
444        tracker.discard(2);
445        tracker.discard(7);
446        assert_eq!(tracker.uninitialized_ranges.len(), 2);
447        assert_eq!(tracker.uninitialized_ranges[0], 0..3);
448        assert_eq!(tracker.uninitialized_ranges[1], 7..10);
449    }
450
451    #[test]
452    fn discard_merges_ranges() {
453        let mut tracker = Tracker::new(10);
454        tracker.drain(3..4);
455        tracker.discard(3);
456        assert_eq!(tracker.uninitialized_ranges.len(), 1);
457        assert_eq!(tracker.uninitialized_ranges[0], 0..10);
458    }
459}
460
461#[cfg(all(test, not(target_family = "wasm")))]
462mod proptest {
463    use alloc::{vec, vec::Vec};
464    use core::ops::Range;
465
466    use proptest::prelude::*;
467
468    type Tracker = super::InitTracker<u32>;
469
470    /// A simple reference model for [`super::InitTracker`].
471    ///
472    /// `initialized[i]` is true iff position `i` is initialized.
473    #[derive(Debug, Clone)]
474    struct Model {
475        initialized: Vec<bool>,
476    }
477
478    impl Model {
479        fn new(size: u32) -> Self {
480            Model {
481                initialized: vec![false; size as usize],
482            }
483        }
484
485        fn drain(&mut self, range: Range<u32>) {
486            for i in range {
487                self.initialized[i as usize] = true;
488            }
489        }
490
491        fn discard(&mut self, pos: u32) {
492            self.initialized[pos as usize] = false;
493        }
494
495        fn size(&self) -> u32 {
496            self.initialized.len() as u32
497        }
498
499        /// The uninitialized positions within `query`, in ascending order.
500        fn uninitialized_positions(&self, query: Range<u32>) -> Vec<u32> {
501            query.filter(|&i| !self.initialized[i as usize]).collect()
502        }
503    }
504
505    #[derive(Debug, Clone)]
506    enum Op {
507        Drain(Range<u32>),
508        Discard(u32),
509    }
510
511    /// A normalized (`start <= end`) subrange of `0..=size`, possibly empty.
512    fn range_strategy(size: u32) -> impl Strategy<Value = Range<u32>> {
513        (0..=size, 0..=size).prop_map(|(a, b)| if a <= b { a..b } else { b..a })
514    }
515
516    fn op_strategy(size: u32) -> impl Strategy<Value = Op> {
517        prop_oneof![
518            3 => range_strategy(size).prop_map(Op::Drain),
519            // `discard` operates on a single existing position, so it must be
520            // in bounds (`size` is always >= 1 here).
521            1 => (0..size).prop_map(Op::Discard),
522        ]
523    }
524
525    /// The set of positions the tracker treats as uninitialized, reconstructed
526    /// from its internal range list.
527    fn tracker_uninit(tracker: &Tracker, size: u32) -> Vec<bool> {
528        let mut set = vec![false; size as usize];
529        for r in tracker.uninitialized_ranges.iter() {
530            for i in r.clone() {
531                set[i as usize] = true;
532            }
533        }
534        set
535    }
536
537    /// Assert the tracker's internal state matches the model.
538    ///
539    /// The tracked set of uninitialized positions must exactly match the model.
540    ///
541    /// The internal range list must uphold the invariant that it contains non-empty,
542    /// in-bounds ranges, not overlapping or adjacent, and sorted by end. The non-adjacency
543    /// property (alternatively, that ranges must be merged whenever possible) is required
544    /// by `discard`, which could otherwise corrupt an adjacency into an overlap.
545    fn assert_state_matches_model(tracker: &Tracker, model: &Model) {
546        let ranges = &tracker.uninitialized_ranges;
547        let mut prev_end = None;
548        for (i, r) in ranges.iter().enumerate() {
549            assert!(r.start < r.end, "empty range {r:?} at index {i}");
550            assert!(
551                r.end <= model.size(),
552                "out-of-bounds range {r:?} at index {i}"
553            );
554            if let Some(prev_end) = prev_end {
555                assert!(
556                    prev_end < r.start,
557                    "range {r:?} at index {i} is not strictly after the previous range \
558                     (previous end {prev_end}); the list must stay sorted by end and fully \
559                     merged (no overlapping or adjacent ranges)",
560                );
561            }
562            prev_end = Some(r.end);
563        }
564
565        let tracker_uninit = tracker_uninit(tracker, model.size());
566        let model_uninit: Vec<bool> = model.initialized.iter().map(|&i| !i).collect();
567        assert_eq!(
568            tracker_uninit, model_uninit,
569            "tracker's uninitialized set diverged from the model",
570        );
571    }
572
573    /// Verify that `check` and `uninitialized` return results matching the model.
574    fn assert_queries_match_model(tracker: &mut Tracker, model: &Model, query: Range<u32>) {
575        let uninit = model.uninitialized_positions(query.clone());
576
577        // `check` is allowed to be loose: its result is only used to decide whether any
578        // initialization may be needed, not directly as the target of a clear. So a
579        // `Some(r)` from `check` may be larger than necessary, but must stay within the
580        // query and cover every uninitialized position in it; `None` must only be returned
581        // if the entire query range is initialized.
582        match tracker.check(query.clone()) {
583            None => assert!(
584                uninit.is_empty(),
585                "check({query:?}) returned None but positions {uninit:?} are uninitialized",
586            ),
587            Some(r) => {
588                assert!(
589                    r.start >= query.start && r.end <= query.end,
590                    "check({query:?}) returned {r:?} outside the query range",
591                );
592                if let (Some(&first), Some(&last)) = (uninit.first(), uninit.last()) {
593                    assert!(
594                        r.start <= first && r.end > last,
595                        "check({query:?}) = {r:?} does not cover all uninitialized positions \
596                         (first {first}, last {last})",
597                    );
598                }
599            }
600        }
601
602        // The ranges returned by `uninitialized` will be zero-filled, so it must be exact,
603        // otherwise we could clobber valid data.
604        let iter_positions: Vec<u32> = tracker
605            .uninitialized(query.clone())
606            .inspect(|r| {
607                assert!(
608                    r.start >= query.start && r.end <= query.end,
609                    "uninitialized({query:?}) yielded out-of-range subrange {r:?}",
610                );
611            })
612            .flatten()
613            .collect();
614        assert_eq!(
615            iter_positions, uninit,
616            "uninitialized({query:?}) did not enumerate exactly the uninitialized positions",
617        );
618    }
619
620    proptest! {
621        #[test]
622        fn tracker_matches_model(
623            (size, ops, queries) in (1u32..=64).prop_flat_map(|size| {
624                (
625                    Just(size),
626                    proptest::collection::vec(op_strategy(size), 0..64),
627                    proptest::collection::vec(range_strategy(size), 1..16),
628                )
629            })
630        ) {
631            let mut tracker = Tracker::new(size);
632            let mut model = Model::new(size);
633            assert_state_matches_model(&tracker, &model);
634
635            for op in ops {
636                match op {
637                    Op::Drain(range) => {
638                        // Fully consume the drain iterator, as callers do.
639                        tracker.drain(range.clone()).for_each(drop);
640                        model.drain(range);
641                    }
642                    Op::Discard(pos) => {
643                        tracker.discard(pos);
644                        model.discard(pos);
645                    }
646                }
647                assert_state_matches_model(&tracker, &model);
648            }
649
650            for query in queries {
651                assert_queries_match_model(&mut tracker, &model, query);
652            }
653        }
654    }
655}