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
use std::borrow::Cow;
/// Utility for dealing with buffers containing raw 2D texture data.
#[derive(Clone, Debug)]
pub struct Texture2DBufferInfo {
/// How many bytes per row contain actual data.
pub bytes_per_row_unpadded: u32,
/// How many bytes per row are required to be allocated in total.
///
/// Padding bytes are always at the end of a row.
pub bytes_per_row_padded: u32,
/// Size required for an unpadded buffer.
pub buffer_size_unpadded: wgpu::BufferAddress,
/// Size required for a padded buffer as it is read/written from/to the GPU.
pub buffer_size_padded: wgpu::BufferAddress,
}
impl Texture2DBufferInfo {
/// Retrieves 2D texture buffer info for a given format & texture size.
///
/// If a single buffer is not possible for all aspects of the texture format, all sizes will be zero.
#[inline]
pub fn new(format: wgpu::TextureFormat, extent: wgpu::Extent3d) -> Self {
let block_dimensions = format.block_dimensions();
let width_blocks = extent.width / block_dimensions.0;
let height_blocks = extent.height / block_dimensions.1;
let block_size = format
.block_copy_size(Some(wgpu::TextureAspect::All))
.unwrap_or(0); // This happens if we can't have a single buffer.
let bytes_per_row_unpadded = width_blocks * block_size;
let bytes_per_row_padded =
wgpu::util::align_to(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
Self {
bytes_per_row_unpadded,
bytes_per_row_padded,
buffer_size_unpadded: (bytes_per_row_unpadded * height_blocks) as wgpu::BufferAddress,
buffer_size_padded: (bytes_per_row_padded * height_blocks) as wgpu::BufferAddress,
}
}
pub fn from_texture(texture: &wgpu::Texture) -> Self {
Self::new(texture.format(), texture.size())
}
#[inline]
pub fn num_rows(&self) -> u32 {
self.buffer_size_padded as u32 / self.bytes_per_row_padded
}
/// Removes the padding from a buffer containing gpu texture data.
///
/// The passed in buffer is to be expected to be exactly of size [`Texture2DBufferInfo::buffer_size_padded`].
///
/// Note that if you're passing in gpu data, there no alignment guarantees on the returned slice,
/// do NOT convert it using [`bytemuck`]. Use [`Texture2DBufferInfo::remove_padding_and_convert`] instead.
pub fn remove_padding<'a>(&self, buffer: &'a [u8]) -> Cow<'a, [u8]> {
re_tracing::profile_function!();
assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded);
if self.bytes_per_row_padded == self.bytes_per_row_unpadded {
return Cow::Borrowed(buffer);
}
let mut unpadded_buffer = Vec::with_capacity(self.buffer_size_unpadded as _);
for row in 0..self.num_rows() {
let offset = (self.bytes_per_row_padded * row) as usize;
unpadded_buffer.extend_from_slice(
&buffer[offset..(offset + self.bytes_per_row_unpadded as usize)],
);
}
unpadded_buffer.into()
}
/// Removes the padding from a buffer containing gpu texture data and remove convert to a given type.
///
/// The passed in buffer is to be expected to be exactly of size [`Texture2DBufferInfo::buffer_size_padded`].
///
/// The unpadded row size is expected to be a multiple of the size of the target type.
/// (Which means that, while uncommon, it technically doesn't need to be as big as a block in the pixel - this can be useful for e.g. packing wide bitfields)
pub fn remove_padding_and_convert<T: bytemuck::Pod>(&self, buffer: &[u8]) -> Vec<T> {
re_tracing::profile_function!();
assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded);
assert!(self.bytes_per_row_unpadded % std::mem::size_of::<T>() as u32 == 0);
// Due to https://github.com/gfx-rs/wgpu/issues/3508 the data might be completely unaligned,
// so much, that we can't even interpret it as e.g. a u32 slice.
// Therefore, we have to do a copy of the data regardless of whether it's padded or not.
let mut unpadded_buffer: Vec<T> = vec![
T::zeroed();
(self.num_rows() * self.bytes_per_row_unpadded / std::mem::size_of::<T>() as u32)
as usize
]; // TODO(andreas): Consider using unsafe set_len() instead of vec![] to avoid zeroing the memory.
// The copy has to happen on a u8 slice, because any other type would assume some alignment that we can't guarantee because of the above.
let unpadded_buffer_u8_view = bytemuck::cast_slice_mut(&mut unpadded_buffer);
for row in 0..self.num_rows() {
let offset_padded = (self.bytes_per_row_padded * row) as usize;
let offset_unpadded = (self.bytes_per_row_unpadded * row) as usize;
unpadded_buffer_u8_view
[offset_unpadded..(offset_unpadded + self.bytes_per_row_unpadded as usize)]
.copy_from_slice(
&buffer[offset_padded..(offset_padded + self.bytes_per_row_unpadded as usize)],
);
}
unpadded_buffer
}
}
pub fn is_float_filterable(format: wgpu::TextureFormat, device_features: wgpu::Features) -> bool {
format
.guaranteed_format_features(device_features)
.flags
.contains(wgpu::TextureFormatFeatureFlags::FILTERABLE)
}