demo_vk/graphics/vulkan/allocator/
mod.rs

1mod allocation_requirements;
2pub mod block;
3mod composable_allocator;
4mod humanized_size;
5pub mod owned_block;
6
7use {
8    self::{
9        allocation_requirements::AllocationRequirements,
10        composable_allocator::ComposableAllocator,
11        humanized_size::HumanizedSize,
12    },
13    crate::{
14        graphics::vulkan::{raii, Block},
15        trace,
16    },
17    anyhow::{bail, Context, Result},
18    ash::vk,
19    std::{
20        sync::{
21            mpsc::{Sender, SyncSender},
22            Arc,
23        },
24        thread::JoinHandle,
25    },
26};
27
28/// A request from the allocator to the central allocation thread.
29enum Request {
30    /// Request an allocation with the specified requirements.
31    Allocate(AllocationRequirements, SyncSender<Result<Block>>),
32
33    /// Free a block.
34    Free(Block),
35
36    /// Shutdown the allocation thread.
37    ShutDown,
38}
39
40/// The Vulkan device memory allocator.
41///
42/// # Performance
43///
44/// All Vulkan allocations are serialized into a single queue served by a
45/// background thread. This is to simplify the allocator implementation (it's
46/// single-threaded) but it means that allocating tons of memory from many
47/// threads could cause bottlenecks. In practice, this doesn't seem to matter
48/// because device allocations are typically fairly long-lived.
49///
50/// # Device Memory Usage
51///
52/// The allocator implementation attempts to only allocate large blocks of
53/// Device memory, then subdivide them to fit individual allocation requests.
54/// This logic is hosted in the private composable_allocator module.
55pub struct Allocator {
56    logical_device: Arc<raii::Device>,
57    client: Sender<Request>,
58    allocation_thread: Option<JoinHandle<()>>,
59    memory_properties: vk::PhysicalDeviceMemoryProperties,
60}
61
62impl Allocator {
63    pub fn new(
64        logical_device: Arc<raii::Device>,
65        physical_device: vk::PhysicalDevice,
66    ) -> Result<Self> {
67        let memory_properties = unsafe {
68            logical_device
69                .ash
70                .get_physical_device_memory_properties(physical_device)
71        };
72        let (handle, client) = Self::spawn_allocator_thread(
73            logical_device.clone(),
74            memory_properties,
75        );
76        Ok(Self {
77            logical_device,
78            client,
79            allocation_thread: Some(handle),
80            memory_properties,
81        })
82    }
83
84    /// Allocates device memory according to the given requirements.
85    pub fn allocate_memory(
86        &self,
87        requirements: &vk::MemoryRequirements,
88        memory_property_flags: vk::MemoryPropertyFlags,
89        memory_allocate_flags: vk::MemoryAllocateFlags,
90        dedicated: bool,
91    ) -> Result<Block> {
92        let requirements = AllocationRequirements::new(
93            &self.memory_properties,
94            requirements,
95            memory_property_flags,
96            memory_allocate_flags,
97            dedicated,
98        )?;
99
100        // Send the memory allocation request to the allocator thread
101        let (response_sender, response) =
102            std::sync::mpsc::sync_channel::<Result<Block>>(1);
103        if self
104            .client
105            .send(Request::Allocate(requirements, response_sender))
106            .is_err()
107        {
108            bail!(trace!("Unable to send allocation request!")());
109        }
110
111        // wait for the response
112        response
113            .recv()
114            .with_context(trace!("Error while receiving response!"))?
115    }
116
117    /// Free the allocated block.
118    pub fn free(&self, block: &Block) {
119        if self.client.send(Request::Free(*block)).is_err() {
120            log::error!("Error while attempting to free memory: {:#?}", block);
121        }
122    }
123
124    /// Spawns the allocator thread and returns the join handle and request
125    /// client.
126    fn spawn_allocator_thread(
127        logical_device: Arc<raii::Device>,
128        memory_properties: vk::PhysicalDeviceMemoryProperties,
129    ) -> (JoinHandle<()>, Sender<Request>) {
130        let (sender, receiver) = std::sync::mpsc::channel::<Request>();
131        let handle = std::thread::spawn(move || {
132            let mut allocator = composable_allocator::create_system_allocator(
133                logical_device,
134                memory_properties,
135            );
136            'main: loop {
137                let allocation_request = if let Ok(request) = receiver.recv() {
138                    request
139                } else {
140                    log::warn!("Memory allocation client hung up!");
141                    break 'main;
142                };
143
144                match allocation_request {
145                    Request::Allocate(requirements, response) => {
146                        let result = allocator.allocate_memory(requirements);
147                        if let Err(error) = response.send(result) {
148                            log::error!(
149                                "Unable to send block to requester! {}",
150                                error
151                            );
152                            break 'main;
153                        }
154                    }
155                    Request::Free(block) => {
156                        allocator.free_memory(&block);
157                    }
158                    Request::ShutDown => {
159                        log::trace!("Shutdown requested");
160                        break 'main;
161                    }
162                }
163            }
164            log::trace!("Device memory allocator shut down.");
165        });
166        (handle, sender)
167    }
168}
169
170impl std::fmt::Debug for Allocator {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.debug_struct("Allocator").finish_non_exhaustive()
173    }
174}
175
176impl Drop for Allocator {
177    fn drop(&mut self) {
178        if self.client.send(Request::ShutDown).is_err() {
179            log::error!("Error while sending shutdown request!");
180        }
181        let allocator_thread_result =
182            self.allocation_thread.take().unwrap().join();
183        if let Err(error) = allocator_thread_result {
184            log::error!("Error in allocator thread!\n\n{:?}", error);
185        }
186    }
187}