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)
}