gpu_allocator/
lib.rs

1//! This crate provides a fully written in Rust memory allocator for Vulkan, DirectX 12 and Metal.
2//!
3//! # Setting up the Vulkan memory allocator
4//!
5//! ```no_run
6//! # #[cfg(feature = "vulkan")]
7//! # fn main() {
8//! use gpu_allocator::vulkan::*;
9//! # use ash::vk;
10//! # let device = todo!();
11//! # let instance = todo!();
12//! # let physical_device = todo!();
13//!
14//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
15//!     instance,
16//!     device,
17//!     physical_device,
18//!     debug_settings: Default::default(),
19//!     buffer_device_address: true,  // Ideally, check the BufferDeviceAddressFeatures struct.
20//!     allocation_sizes: Default::default(),
21//! });
22//! # }
23//! # #[cfg(not(feature = "vulkan"))]
24//! # fn main() {}
25//! ```
26//!
27//! # Simple Vulkan allocation example
28//!
29//! ```no_run
30//! # #[cfg(feature = "vulkan")]
31//! # fn main() {
32//! use gpu_allocator::vulkan::*;
33//! use gpu_allocator::MemoryLocation;
34//! # use ash::vk;
35//! # let device = todo!();
36//! # let instance = todo!();
37//! # let physical_device = todo!();
38//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
39//! #     instance,
40//! #     device,
41//! #     physical_device,
42//! #     debug_settings: Default::default(),
43//! #     buffer_device_address: true,  // Ideally, check the BufferDeviceAddressFeatures struct.
44//! #     allocation_sizes: Default::default(),
45//! # }).unwrap();
46//!
47//! // Setup vulkan info
48//! let vk_info = vk::BufferCreateInfo::default()
49//!     .size(512)
50//!     .usage(vk::BufferUsageFlags::STORAGE_BUFFER);
51//!
52//! let buffer = unsafe { device.create_buffer(&vk_info, None) }.unwrap();
53//! let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
54//!
55//! let allocation = allocator
56//!     .allocate(&AllocationCreateDesc {
57//!         name: "Example allocation",
58//!         requirements,
59//!         location: MemoryLocation::CpuToGpu,
60//!         linear: true, // Buffers are always linear
61//!         allocation_scheme: AllocationScheme::GpuAllocatorManaged,
62//!     }).unwrap();
63//!
64//! // Bind memory to the buffer
65//! unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()).unwrap() };
66//!
67//! // Cleanup
68//! allocator.free(allocation).unwrap();
69//! unsafe { device.destroy_buffer(buffer, None) };
70//! # }
71//! # #[cfg(not(feature = "vulkan"))]
72//! # fn main() {}
73//! ```
74//!
75//! # Setting up the D3D12 memory allocator
76//!
77//! ```no_run
78//! # #[cfg(feature = "d3d12")]
79//! # fn main() {
80//! use gpu_allocator::d3d12::*;
81//! # let device = todo!();
82//!
83//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
84//!     device: ID3D12DeviceVersion::Device(device),
85//!     debug_settings: Default::default(),
86//!     allocation_sizes: Default::default(),
87//! });
88//! # }
89//! # #[cfg(not(feature = "d3d12"))]
90//! # fn main() {}
91//! ```
92//!
93//! # Simple d3d12 allocation example
94//!
95//! ```no_run
96//! # #[cfg(feature = "d3d12")]
97//! # fn main() -> windows::core::Result<()> {
98//! use gpu_allocator::d3d12::*;
99//! use gpu_allocator::MemoryLocation;
100//! # use windows::Win32::Graphics::{Dxgi, Direct3D12};
101//! # let device = todo!();
102//!
103//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
104//! #     device: ID3D12DeviceVersion::Device(device),
105//! #     debug_settings: Default::default(),
106//! #     allocation_sizes: Default::default(),
107//! # }).unwrap();
108//!
109//! let buffer_desc = Direct3D12::D3D12_RESOURCE_DESC {
110//!     Dimension: Direct3D12::D3D12_RESOURCE_DIMENSION_BUFFER,
111//!     Alignment: 0,
112//!     Width: 512,
113//!     Height: 1,
114//!     DepthOrArraySize: 1,
115//!     MipLevels: 1,
116//!     Format: Dxgi::Common::DXGI_FORMAT_UNKNOWN,
117//!     SampleDesc: Dxgi::Common::DXGI_SAMPLE_DESC {
118//!         Count: 1,
119//!         Quality: 0,
120//!     },
121//!     Layout: Direct3D12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
122//!     Flags: Direct3D12::D3D12_RESOURCE_FLAG_NONE,
123//! };
124//! let allocation_desc = AllocationCreateDesc::from_d3d12_resource_desc(
125//!     &allocator.device(),
126//!     &buffer_desc,
127//!     "Example allocation",
128//!     MemoryLocation::GpuOnly,
129//! );
130//! let allocation = allocator.allocate(&allocation_desc).unwrap();
131//! let mut resource: Option<Direct3D12::ID3D12Resource> = None;
132//! let hr = unsafe {
133//!     device.CreatePlacedResource(
134//!         allocation.heap(),
135//!         allocation.offset(),
136//!         &buffer_desc,
137//!         Direct3D12::D3D12_RESOURCE_STATE_COMMON,
138//!         None,
139//!         &mut resource,
140//!     )
141//! }?;
142//!
143//! // Cleanup
144//! drop(resource);
145//! allocator.free(allocation).unwrap();
146//! # Ok(())
147//! # }
148//! # #[cfg(not(feature = "d3d12"))]
149//! # fn main() {}
150//! ```
151//!
152//! # Setting up the Metal memory allocator
153//!
154//! ```no_run
155//! # #[cfg(feature = "metal")]
156//! # fn main() {
157//! use gpu_allocator::metal::*;
158//! # let device = objc2_metal::MTLCreateSystemDefaultDevice().expect("No MTLDevice found");
159//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
160//!     device: device.clone(),
161//!     debug_settings: Default::default(),
162//!     allocation_sizes: Default::default(),
163//!     create_residency_set: false,
164//! });
165//! # }
166//! # #[cfg(not(feature = "metal"))]
167//! # fn main() {}
168//! ```
169//!
170//! # Simple Metal allocation example
171//!
172//! ```no_run
173//! # #[cfg(feature = "metal")]
174//! # fn main() {
175//! use gpu_allocator::metal::*;
176//! use gpu_allocator::MemoryLocation;
177//! # let device = objc2_metal::MTLCreateSystemDefaultDevice().expect("No MTLDevice found");
178//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
179//! #     device: device.clone(),
180//! #     debug_settings: Default::default(),
181//! #     allocation_sizes: Default::default(),
182//! #    create_residency_set: false,
183//! # })
184//! # .unwrap();
185//! let allocation_desc = AllocationCreateDesc::buffer(
186//!     &device,
187//!     "Example allocation",
188//!     512, // size in bytes
189//!     MemoryLocation::GpuOnly,
190//! );
191//! let allocation = allocator.allocate(&allocation_desc).unwrap();
192//! # use objc2_metal::MTLHeap;
193//! let heap = unsafe { allocation.heap() };
194//! let resource = unsafe {
195//!     heap.newBufferWithLength_options_offset(
196//!         allocation.size() as usize,
197//!         heap.resourceOptions(),
198//!         allocation.offset() as usize,
199//!     )
200//! }
201//! .unwrap();
202//!
203//! // Cleanup
204//! drop(resource);
205//! allocator.free(&allocation).unwrap();
206//! # }
207//! # #[cfg(not(feature = "metal"))]
208//! # fn main() {}
209//! ```
210#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)]
211#![warn(
212    clippy::alloc_instead_of_core,
213    clippy::std_instead_of_alloc,
214    clippy::std_instead_of_core
215)]
216#![cfg_attr(not(feature = "std"), no_std)]
217
218#[macro_use]
219extern crate alloc;
220
221#[cfg(all(not(feature = "std"), feature = "visualizer"))]
222compile_error!("Cannot enable `visualizer` feature in `no_std` environment.");
223
224#[cfg(not(any(feature = "std", feature = "hashbrown")))]
225compile_error!("Either `std` or `hashbrown` feature must be enabled");
226
227mod result;
228pub use result::*;
229
230pub(crate) mod allocator;
231
232pub use allocator::{AllocationReport, AllocatorReport, MemoryBlockReport};
233
234#[cfg(feature = "visualizer")]
235pub mod visualizer;
236
237#[cfg(feature = "vulkan")]
238pub mod vulkan;
239
240#[cfg(all(windows, feature = "d3d12"))]
241pub mod d3d12;
242
243#[cfg(all(target_vendor = "apple", feature = "metal"))]
244pub mod metal;
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
247pub enum MemoryLocation {
248    /// The allocated resource is stored at an unknown memory location; let the driver decide what's the best location
249    Unknown,
250    /// Store the allocation in GPU only accessible memory - typically this is the faster GPU resource and this should be
251    /// where most of the allocations live.
252    GpuOnly,
253    /// Memory useful for uploading data to the GPU and potentially for constant buffers
254    CpuToGpu,
255    /// Memory useful for CPU readback of data
256    GpuToCpu,
257}
258
259#[non_exhaustive]
260#[derive(Copy, Clone, Debug)]
261pub struct AllocatorDebugSettings {
262    /// Logs out debugging information about the various heaps the current device has on startup
263    pub log_memory_information: bool,
264    /// Logs out all memory leaks on shutdown with log level Warn
265    pub log_leaks_on_shutdown: bool,
266    /// Stores a copy of the full backtrace for every allocation made, this makes it easier to debug leaks
267    /// or other memory allocations, but storing stack traces has a RAM overhead so should be disabled
268    /// in shipping applications.
269    #[cfg(feature = "std")]
270    pub store_stack_traces: bool,
271    /// Log out every allocation as it's being made with log level Debug, rather spammy so off by default
272    pub log_allocations: bool,
273    /// Log out every free that is being called with log level Debug, rather spammy so off by default
274    pub log_frees: bool,
275    /// Log out stack traces when either `log_allocations` or `log_frees` is enabled.
276    #[cfg(feature = "std")]
277    pub log_stack_traces: bool,
278}
279
280impl Default for AllocatorDebugSettings {
281    fn default() -> Self {
282        Self {
283            log_memory_information: false,
284            log_leaks_on_shutdown: true,
285            #[cfg(feature = "std")]
286            store_stack_traces: false,
287            log_allocations: false,
288            log_frees: false,
289            #[cfg(feature = "std")]
290            log_stack_traces: false,
291        }
292    }
293}
294
295/// The sizes of the memory blocks that the allocator will create.
296///
297/// Useful for tuning the allocator to your application's needs. For example most games will be fine with the default
298/// values, but eg. an app might want to use smaller block sizes to reduce the amount of memory used.
299///
300/// Clamped between 4MB and 256MB, and rounds up to the nearest multiple of 4MB for alignment reasons.
301///
302/// Note that these limits only apply to shared memory blocks that can hold multiple allocations.
303/// If an allocation does not fit within the corresponding maximum block size, it will be placed
304/// in a dedicated memory block holding only this allocation, without limitations other than what
305/// the underlying hardware and driver are able to provide.
306///
307/// # Fixed or growable block size
308///
309/// This structure represents ranges of allowed sizes for shared memory blocks.
310/// By default, if the upper bounds are not extended using `with_max_*_memblock_size`,
311/// the allocator will be configured to use a fixed memory block size for shared
312/// allocations.
313///
314/// Otherwise, the allocator will pick a memory block size within the specifed
315/// range, depending on the number of existing allocations for the memory
316/// type.
317///
318/// As a rule of thumb, the allocator will start with the minimum block size
319/// and double the size with each new allocation, up to the specified maximum
320/// block size. This growth is tracked independently for each memory type.
321/// The block size also decreases when blocks are deallocated.
322///
323/// # Example
324///
325/// ```
326/// use gpu_allocator::AllocationSizes;
327/// const MB: u64 = 1024 * 1024;
328/// // This configuration uses fixed memory block sizes.
329/// let fixed = AllocationSizes::new(256 * MB, 64 * MB);
330///
331/// // This configuration starts with 8MB memory blocks
332/// // and grows the block size of a given memory type each
333/// // time a new allocation is needed, up to a limit of
334/// // 256MB for device memory and 64MB for host memory.
335/// let growing = AllocationSizes::new(8 * MB, 8 * MB)
336///     .with_max_device_memblock_size(256 * MB)
337///     .with_max_host_memblock_size(64 * MB);
338/// ```
339#[derive(Clone, Copy, Debug)]
340pub struct AllocationSizes {
341    /// The initial size for device memory blocks.
342    ///
343    /// The size of new device memory blocks doubles each time a new block is needed, up to
344    /// [`AllocationSizes::max_device_memblock_size`].
345    ///
346    /// Defaults to 256MB.
347    min_device_memblock_size: u64,
348    /// The maximum size for device memory blocks.
349    ///
350    /// Defaults to the value of [`AllocationSizes::min_device_memblock_size`].
351    max_device_memblock_size: u64,
352    /// The initial size for host memory blocks.
353    ///
354    /// The size of new host memory blocks doubles each time a new block is needed, up to
355    /// [`AllocationSizes::max_host_memblock_size`].
356    ///
357    /// Defaults to 64MB.
358    min_host_memblock_size: u64,
359    /// The maximum size for host memory blocks.
360    ///
361    /// Defaults to the value of [`AllocationSizes::min_host_memblock_size`].
362    max_host_memblock_size: u64,
363}
364
365impl AllocationSizes {
366    /// Sets the minimum device and host memory block sizes.
367    ///
368    /// The maximum block sizes are initialized to the minimum sizes and
369    /// can be increased using [`AllocationSizes::with_max_device_memblock_size`] and
370    /// [`AllocationSizes::with_max_host_memblock_size`].
371    pub fn new(device_memblock_size: u64, host_memblock_size: u64) -> Self {
372        let device_memblock_size = Self::adjust_memblock_size(device_memblock_size, "Device");
373        let host_memblock_size = Self::adjust_memblock_size(host_memblock_size, "Host");
374
375        Self {
376            min_device_memblock_size: device_memblock_size,
377            max_device_memblock_size: device_memblock_size,
378            min_host_memblock_size: host_memblock_size,
379            max_host_memblock_size: host_memblock_size,
380        }
381    }
382
383    /// Sets the maximum device memblock size, in bytes.
384    pub fn with_max_device_memblock_size(mut self, size: u64) -> Self {
385        self.max_device_memblock_size =
386            Self::adjust_memblock_size(size, "Device").max(self.min_device_memblock_size);
387
388        self
389    }
390
391    /// Sets the maximum host memblock size, in bytes.
392    pub fn with_max_host_memblock_size(mut self, size: u64) -> Self {
393        self.max_host_memblock_size =
394            Self::adjust_memblock_size(size, "Host").max(self.min_host_memblock_size);
395
396        self
397    }
398
399    fn adjust_memblock_size(size: u64, kind: &str) -> u64 {
400        const MB: u64 = 1024 * 1024;
401
402        let size = size.clamp(4 * MB, 256 * MB);
403
404        if size % (4 * MB) == 0 {
405            return size;
406        }
407
408        let val = size / (4 * MB) + 1;
409        let new_size = val * 4 * MB;
410        log::warn!(
411            "{kind} memory block size must be a multiple of 4MB, clamping to {}MB",
412            new_size / MB
413        );
414
415        new_size
416    }
417
418    /// Used internally to decide the size of a shared memory block
419    /// based within the allowed range, based on the number of
420    /// existing allocations. The more blocks there already are
421    /// (where the requested allocation didn't fit), the larger
422    /// the returned memory block size is going to be (up to
423    /// `max_*_memblock_size`).
424    pub(crate) fn get_memblock_size(&self, is_host: bool, count: usize) -> u64 {
425        let (min_size, max_size) = if is_host {
426            (self.min_host_memblock_size, self.max_host_memblock_size)
427        } else {
428            (self.min_device_memblock_size, self.max_device_memblock_size)
429        };
430
431        // The ranges are clamped to 4MB..256MB so we never need to
432        // shift by more than 7 bits. Clamping here to avoid having
433        // to worry about overflows.
434        let shift = count.min(7) as u64;
435        (min_size << shift).min(max_size)
436    }
437}
438
439impl Default for AllocationSizes {
440    fn default() -> Self {
441        const MB: u64 = 1024 * 1024;
442        Self {
443            min_device_memblock_size: 256 * MB,
444            max_device_memblock_size: 256 * MB,
445            min_host_memblock_size: 64 * MB,
446            max_host_memblock_size: 64 * MB,
447        }
448    }
449}