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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
use super::align_up;
use crate::{
buffer::BufferContents, macros::try_opt, memory::DeviceAlignment, DeviceSize, NonZeroDeviceSize,
};
use std::{
alloc::Layout,
error::Error,
fmt::{Debug, Display, Formatter, Result as FmtResult},
hash::Hash,
mem,
};
/// Vulkan analog of std's [`Layout`], represented using [`DeviceSize`]s.
///
/// Unlike `Layout`s, `DeviceLayout`s are required to have non-zero size.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DeviceLayout {
size: NonZeroDeviceSize,
alignment: DeviceAlignment,
}
const _: () = assert!(mem::size_of::<DeviceSize>() >= mem::size_of::<usize>());
const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize);
impl DeviceLayout {
/// The maximum size of a memory block after its layout's size has been rounded up to the
/// nearest multiple of its layout's alignment.
///
/// This invariant is enforced to avoid arithmetic overflows when constructing layouts and when
/// allocating memory. Any layout that doesn't uphold this invariant will therefore *lead to
/// undefined behavior*.
pub const MAX_SIZE: DeviceSize = DeviceAlignment::MAX.as_devicesize() - 1;
/// Creates a new `DeviceLayout` from a [`Layout`], or returns [`None`] if the `Layout` has
/// zero size.
#[inline]
pub const fn from_layout(layout: Layout) -> Option<DeviceLayout> {
let (size, alignment) = Self::size_alignment_from_layout(&layout);
if let Some(size) = NonZeroDeviceSize::new(size) {
// SAFETY: Under the precondition that `usize` can't overflow `DeviceSize`, which we
// checked above, `Layout`'s overflow-invariant is the same if not stricter than that
// of `DeviceLayout`.
Some(unsafe { DeviceLayout::new_unchecked(size, alignment) })
} else {
None
}
}
/// Converts the `DeviceLayout` into a [`Layout`], or returns [`None`] if the `DeviceLayout`
/// doesn't meet the invariants of `Layout`.
#[inline]
pub const fn into_layout(self) -> Option<Layout> {
let (size, alignment) = (self.size(), self.alignment().as_devicesize());
#[cfg(target_pointer_width = "64")]
{
// SAFETY: Under the precondition that `DeviceSize` can't overflow `usize`, which we
// checked above, `DeviceLayout`'s overflow-invariant is the same if not stricter that
// of `Layout`.
Some(unsafe { Layout::from_size_align_unchecked(size as usize, alignment as usize) })
}
#[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))]
{
if size > usize::MAX as DeviceSize || alignment > usize::MAX as DeviceSize {
None
} else if let Ok(layout) = Layout::from_size_align(size as usize, alignment as usize) {
Some(layout)
} else {
None
}
}
}
/// Creates a new `DeviceLayout` from the given `size` and `alignment`.
///
/// Returns [`None`] if `size` is zero, `alignment` is not a power of two, or if `size` would
/// exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the nearest multiple of `alignment`.
#[inline]
pub const fn from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option<Self> {
let size = try_opt!(NonZeroDeviceSize::new(size));
let alignment = try_opt!(DeviceAlignment::new(alignment));
DeviceLayout::new(size, alignment)
}
/// Creates a new `DeviceLayout` from the given `size` and `alignment` without doing any
/// checks.
///
/// # Safety
///
/// - `size` must be non-zero.
/// - `alignment` must be a power of two, which also means it must be non-zero.
/// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed
/// [`DeviceLayout::MAX_SIZE`].
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub const unsafe fn from_size_alignment_unchecked(
size: DeviceSize,
alignment: DeviceSize,
) -> Self {
let size = unsafe { NonZeroDeviceSize::new_unchecked(size) };
let alignment = unsafe { DeviceAlignment::new_unchecked(alignment) };
unsafe { DeviceLayout::new_unchecked(size, alignment) }
}
/// Creates a new `DeviceLayout` for a sized `T`.
#[inline]
pub const fn new_sized<T: BufferContents>() -> DeviceLayout {
T::LAYOUT.unwrap_sized()
}
/// Creates a new `DeviceLayout` for an unsized `T` with an unsized tail of `len` elements.
///
/// Returns [`None`] if `len` is zero or if the total size would exceed
/// [`DeviceLayout::MAX_SIZE`], unless the `T` is actually sized, in which case this behaves
/// identically to [`new_sized`] and `len` is ignored.
///
/// [`new_sized`]: Self::new_sized
#[inline]
pub const fn new_unsized<T: BufferContents + ?Sized>(len: DeviceSize) -> Option<DeviceLayout> {
T::LAYOUT.layout_for_len(len)
}
/// Creates a new `DeviceLayout` from the given `size` and `alignment`.
///
/// Returns [`None`] if `size` would exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the
/// nearest multiple of `alignment`.
#[inline]
pub const fn new(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Option<Self> {
if size.get() > Self::max_size_for_alignment(alignment) {
None
} else {
// SAFETY: We checked that the rounded-up size won't exceed `DeviceLayout::MAX_SIZE`.
Some(unsafe { DeviceLayout::new_unchecked(size, alignment) })
}
}
#[inline(always)]
const fn max_size_for_alignment(alignment: DeviceAlignment) -> DeviceSize {
// `DeviceLayout::MAX_SIZE` is `DeviceAlignment::MAX - 1`, so this can't overflow.
DeviceLayout::MAX_SIZE - (alignment.as_devicesize() - 1)
}
/// Creates a new `DeviceLayout` from the given `size` and `alignment` without checking for
/// potential overflow.
///
/// # Safety
///
/// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed
/// [`DeviceLayout::MAX_SIZE`].
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub const unsafe fn new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self {
debug_assert!(size.get() <= Self::max_size_for_alignment(alignment));
DeviceLayout { size, alignment }
}
/// Creates a new `DeviceLayout` for the given `value`.
///
/// Returns [`None`] if the value is zero-sized.
#[inline]
pub fn for_value<T: BufferContents + ?Sized>(value: &T) -> Option<DeviceLayout> {
DeviceLayout::from_layout(Layout::for_value(value))
}
/// Returns the minimum size in bytes for a memory block of this layout.
#[inline]
pub const fn size(&self) -> DeviceSize {
self.size.get()
}
/// Returns the minimum alignment for a memory block of this layout.
#[inline]
pub const fn alignment(&self) -> DeviceAlignment {
self.alignment
}
/// Creates a new `DeviceLayout` from `self` that is also aligned to `alignment` at minimum.
///
/// Returns [`None`] if `self.size()` would overflow [`DeviceLayout::MAX_SIZE`] when rounded up
/// to the nearest multiple of `alignment`.
#[inline]
pub const fn align_to(&self, alignment: DeviceAlignment) -> Option<Self> {
DeviceLayout::new(self.size, DeviceAlignment::max(self.alignment, alignment))
}
/// Returns the amount of padding that needs to be added to `self.size()` such that the result
/// is a multiple of `alignment`.
#[inline]
pub const fn padding_needed_for(&self, alignment: DeviceAlignment) -> DeviceSize {
let size = self.size();
align_up(size, alignment).wrapping_sub(size)
}
/// Creates a new `DeviceLayout` by rounding up `self.size()` to the nearest multiple of
/// `self.alignment()`.
#[inline]
pub const fn pad_to_alignment(&self) -> Self {
// SAFETY: `DeviceLayout`'s invariant guarantees that the rounded up size won't exceed
// `DeviceLayout::MAX_SIZE`.
unsafe { DeviceLayout::new_unchecked(self.padded_size(), self.alignment) }
}
#[inline(always)]
const fn padded_size(&self) -> NonZeroDeviceSize {
let size = align_up(self.size(), self.alignment);
// SAFETY: `DeviceLayout`'s invariant guarantees that the rounded up size won't overflow.
unsafe { NonZeroDeviceSize::new_unchecked(size) }
}
/// Creates a new `DeviceLayout` describing the record for `n` instances of `self`, possibly
/// with padding at the end of each to ensure correct alignment of all instances.
///
/// Returns a tuple consisting of the new layout and the stride, in bytes, of `self`, or
/// returns [`None`] if `n` is zero or when the total size would exceed
/// [`DeviceLayout::MAX_SIZE`].
#[inline]
pub const fn repeat(&self, n: DeviceSize) -> Option<(Self, DeviceSize)> {
let n = try_opt!(NonZeroDeviceSize::new(n));
let stride = self.padded_size();
let size = try_opt!(stride.checked_mul(n));
let layout = try_opt!(DeviceLayout::new(size, self.alignment));
Some((layout, stride.get()))
}
/// Creates a new `DeviceLayout` describing the record for `self` followed by `next`, including
/// potential padding between them to ensure `next` will be properly aligned, but without any
/// trailing padding. You should use [`pad_to_alignment`] after you are done extending the
/// layout with all fields to get a valid `#[repr(C)]` layout.
///
/// The alignments of the two layouts get combined by picking the maximum between them.
///
/// Returns a tuple consisting of the resulting layout as well as the offset, in bytes, of
/// `next`.
///
/// Returns [`None`] on arithmetic overflow or when the total size rounded up to the nearest
/// multiple of the combined alignment would exceed [`DeviceLayout::MAX_SIZE`].
///
/// [`pad_to_alignment`]: Self::pad_to_alignment
#[inline]
pub const fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> {
self.extend_inner(next.size(), next.alignment)
}
/// Same as [`extend`], except it extends with a [`Layout`].
///
/// [`extend`]: Self::extend
#[inline]
pub const fn extend_with_layout(&self, next: Layout) -> Option<(Self, DeviceSize)> {
let (next_size, next_alignment) = Self::size_alignment_from_layout(&next);
self.extend_inner(next_size, next_alignment)
}
const fn extend_inner(
&self,
next_size: DeviceSize,
next_alignment: DeviceAlignment,
) -> Option<(Self, DeviceSize)> {
let padding = self.padding_needed_for(next_alignment);
let offset = try_opt!(self.size.checked_add(padding));
let size = try_opt!(offset.checked_add(next_size));
let alignment = DeviceAlignment::max(self.alignment, next_alignment);
let layout = try_opt!(DeviceLayout::new(size, alignment));
Some((layout, offset.get()))
}
/// Creates a new `DeviceLayout` describing the record for the `previous` [`Layout`] followed
/// by `self`. This function is otherwise the same as [`extend`].
///
/// [`extend`]: Self::extend
#[inline]
pub const fn extend_from_layout(self, previous: &Layout) -> Option<(Self, DeviceSize)> {
let (size, alignment) = Self::size_alignment_from_layout(previous);
let padding = align_up(size, self.alignment).wrapping_sub(size);
let offset = try_opt!(size.checked_add(padding));
let size = try_opt!(self.size.checked_add(offset));
let alignment = DeviceAlignment::max(alignment, self.alignment);
let layout = try_opt!(DeviceLayout::new(size, alignment));
Some((layout, offset))
}
#[inline(always)]
const fn size_alignment_from_layout(layout: &Layout) -> (DeviceSize, DeviceAlignment) {
// We checked that `usize` can't overflow `DeviceSize` above, so this can't truncate.
let (size, alignment) = (layout.size() as DeviceSize, layout.align() as DeviceSize);
// SAFETY: `Layout`'s alignment-invariant guarantees that it is a power of two.
let alignment = unsafe { DeviceAlignment::new_unchecked(alignment) };
(size, alignment)
}
}
impl TryFrom<Layout> for DeviceLayout {
type Error = TryFromLayoutError;
#[inline]
fn try_from(layout: Layout) -> Result<Self, Self::Error> {
DeviceLayout::from_layout(layout).ok_or(TryFromLayoutError)
}
}
impl TryFrom<DeviceLayout> for Layout {
type Error = TryFromDeviceLayoutError;
#[inline]
fn try_from(device_layout: DeviceLayout) -> Result<Self, Self::Error> {
DeviceLayout::into_layout(device_layout).ok_or(TryFromDeviceLayoutError)
}
}
/// Error that can happen when converting a [`Layout`] to a [`DeviceLayout`].
///
/// It occurs when the `Layout` has zero size.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TryFromLayoutError;
impl Error for TryFromLayoutError {}
impl Display for TryFromLayoutError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str("attempted to convert a zero-size `Layout` to a `DeviceLayout`")
}
}
/// Error that can happen when converting a [`DeviceLayout`] to a [`Layout`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TryFromDeviceLayoutError;
impl Error for TryFromDeviceLayoutError {}
impl Display for TryFromDeviceLayoutError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(
"attempted to convert a `DeviceLayout` to a `Layout` which would result in a \
violation of `Layout`'s overflow-invariant",
)
}
}