1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use rafx_api::{RafxDescriptorSetArray, RafxDeviceContext, RafxResult};
use std::collections::VecDeque;
use std::num::Wrapping;

pub type PoolResourceAllocatorAllocFn<T> =
    dyn Fn(&RafxDeviceContext) -> RafxResult<T> + Send + Sync;

/// Implement to customize how PoolAllocator resets and destroys pools
pub trait PooledResourceImpl {
    fn reset(&mut self) -> RafxResult<()>;
}

struct PoolResourceInFlight<T: PooledResourceImpl> {
    pool: T,
    live_until_frame: Wrapping<u32>,
}

/// This handles waiting for N frames to pass before resetting the pool. "Restting" could mean
/// different things depending on the resource. This allocator also has a callback for allocating
/// new pools for use. A maximum pool count should be provided so that an unbounded leak of pools
/// can be detected.
pub struct PooledResourceAllocator<T: PooledResourceImpl> {
    device_context: RafxDeviceContext,

    // Allocates a new pool
    allocate_fn: Box<PoolResourceAllocatorAllocFn<T>>,

    // We are assuming that all pools can survive for the same amount of time so the data in
    // this VecDeque will naturally be orderered such that things that need to be reset sooner
    // are at the front
    in_flight_pools: VecDeque<PoolResourceInFlight<T>>,

    // Pools that have been reset and are ready for allocation
    reset_pools: Vec<T>,

    // All pools that are retired will be reset after N frames
    max_in_flight_frames: Wrapping<u32>,

    // Incremented when on_frame_complete is called
    frame_index: Wrapping<u32>,

    // Number of pools we have created in total
    created_pool_count: u32,

    // Max number of pools to create (sum includes allocated pools, pools in flight, and reset pools
    max_pool_count: u32,
}

impl<T: PooledResourceImpl> PooledResourceAllocator<T> {
    /// Create a pool allocator that will reset resources after N frames. Keep in mind that if for
    /// example you want to push a single resource per frame, up to N+1 resources will exist
    /// in the sink. If max_in_flight_frames is 2, then you would have a resource that has
    /// likely not been submitted to the GPU yet, plus a resource per the N frames that have
    /// been submitted
    pub fn new<F: Fn(&RafxDeviceContext) -> RafxResult<T> + Send + Sync + 'static>(
        device_context: &RafxDeviceContext,
        max_in_flight_frames: u32,
        max_pool_count: u32,
        allocate_fn: F,
    ) -> Self {
        PooledResourceAllocator {
            device_context: device_context.clone(),
            allocate_fn: Box::new(allocate_fn),
            in_flight_pools: Default::default(),
            reset_pools: Default::default(),
            max_in_flight_frames: Wrapping(max_in_flight_frames),
            frame_index: Wrapping(0),
            created_pool_count: 0,
            max_pool_count,
        }
    }

    /// Allocate a pool - either reusing an old one that has been reset or creating a new one. Will
    /// assert that we do not exceed max_pool_count. The pool is allowed to exist until retire_pool
    /// is called. After this point, we will wait for N frames before restting it.
    pub fn allocate_pool(&mut self) -> RafxResult<T> {
        self.reset_pools.pop().map(Ok).unwrap_or_else(|| {
            self.created_pool_count += 1;
            assert!(self.created_pool_count <= self.max_pool_count);
            (self.allocate_fn)(&self.device_context)
        })
    }

    /// Schedule the pool to reset after we complete N frames
    pub fn retire_pool(
        &mut self,
        pool: T,
    ) {
        self.in_flight_pools.push_back(PoolResourceInFlight {
            pool,
            live_until_frame: self.frame_index + self.max_in_flight_frames + Wrapping(1),
        });
    }

    /// Call when we are ready to reset another set of resources, most likely when a frame is
    /// presented or a new frame begins
    pub fn update(&mut self) -> RafxResult<()> {
        self.frame_index += Wrapping(1);

        // Determine how many pools we can drain
        let mut pools_to_drain = 0;
        for in_flight_pool in &self.in_flight_pools {
            // If frame_index matches or exceeds live_until_frame, then the result will be a very
            // high value due to wrapping a negative value to u32::MAX
            if in_flight_pool.live_until_frame - self.frame_index > Wrapping(std::u32::MAX / 2) {
                pools_to_drain += 1;
            } else {
                break;
            }
        }

        // Reset them and add them to the list of pools ready to be allocated
        let pools_to_reset: Vec<_> = self.in_flight_pools.drain(0..pools_to_drain).collect();
        for mut pool_to_reset in pools_to_reset {
            T::reset(&mut pool_to_reset.pool)?;
            self.reset_pools.push(pool_to_reset.pool);
        }

        Ok(())
    }

    /// Immediately destroy everything. We assume the device is idle and nothing is in flight.
    /// Calling this function when the device is not idle could result in a deadlock
    pub fn destroy(&mut self) -> RafxResult<()> {
        for pool in self.in_flight_pools.drain(..) {
            std::mem::drop(pool.pool);
        }

        for pool in self.reset_pools.drain(..) {
            std::mem::drop(pool);
        }

        Ok(())
    }
}

// We assume destroy was called
impl<T: PooledResourceImpl> Drop for PooledResourceAllocator<T> {
    fn drop(&mut self) {
        assert!(self.in_flight_pools.is_empty());
        assert!(self.reset_pools.is_empty())
    }
}

//
// Implementation for descriptor pools
//
impl PooledResourceImpl for RafxDescriptorSetArray {
    fn reset(&mut self) -> RafxResult<()> {
        Ok(())
    }
}

pub type DescriptorSetArrayPoolAllocator = PooledResourceAllocator<RafxDescriptorSetArray>;