use crate::core::*;
#[derive(Debug)]
pub struct MipBuffer {
pub buffer: Vec<u8>,
pub dimensions: MipBufferDimensions,
pub level: u32,
}
#[derive(Debug, Copy, Clone)]
pub struct MipBufferDimensions {
pub width: usize,
pub height: usize,
pub bytes_per_channel: usize,
pub unpadded_bytes_per_row: usize,
pub padded_bytes_per_row: usize,
}
impl MipBufferDimensions {
pub fn new(width: usize, height: usize, bytes_per_channel: usize) -> Self {
let width = width.max(1);
let height = height.max(1);
let unpadded_bytes_per_row = width * bytes_per_channel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
width,
height,
bytes_per_channel,
unpadded_bytes_per_row,
padded_bytes_per_row,
}
}
}
pub async fn generate_and_copy_to_cpu(
device: &wgpu::Device,
queue: &wgpu::Queue,
generator: &dyn MipmapGenerator,
data: &[u8],
texture_descriptor: &wgpu::TextureDescriptor<'_>,
) -> Result<Vec<MipBuffer>, Error> {
let buffer_dimensions = MipBufferDimensions::new(
texture_descriptor.size.width as usize,
texture_descriptor.size.height as usize,
format_bytes_per_channel(&texture_descriptor.format),
);
let texture = device.create_texture(&texture_descriptor);
queue.write_texture(
wgpu::TextureCopyView {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&data,
wgpu::TextureDataLayout {
offset: 0,
bytes_per_row: buffer_dimensions.unpadded_bytes_per_row as u32,
rows_per_image: 0,
},
wgpu::Extent3d {
width: buffer_dimensions.width as u32,
height: buffer_dimensions.height as u32,
depth: 1,
},
);
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
generator.generate(&device, &mut encoder, &texture, &texture_descriptor)?;
let buffers = {
let mut buffers = Vec::new();
for i in 0..texture_descriptor.mip_level_count {
let mip_width = buffer_dimensions.width / 2usize.pow(i);
let mip_height = buffer_dimensions.height / 2usize.pow(i);
let mip_dimensions = MipBufferDimensions::new(
mip_width,
mip_height,
buffer_dimensions.bytes_per_channel,
);
let size = (mip_dimensions.height * mip_dimensions.padded_bytes_per_row) as u64;
let mip_texture_extent = wgpu::Extent3d {
width: mip_width as u32,
height: mip_height as u32,
depth: 1,
};
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size,
usage: wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::MAP_READ,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
wgpu::TextureCopyView {
texture: &texture,
mip_level: i,
origin: wgpu::Origin3d::ZERO,
},
wgpu::BufferCopyView {
buffer: &buffer,
layout: wgpu::TextureDataLayout {
offset: 0,
bytes_per_row: mip_dimensions.padded_bytes_per_row as u32,
rows_per_image: 0,
},
},
mip_texture_extent,
);
buffers.push((buffer, mip_dimensions));
}
buffers
};
queue.submit(std::iter::once(encoder.finish()));
let mut mip_buffers: Vec<MipBuffer> = Vec::new();
for (level, (buffer, buffer_dimensions)) in buffers.iter().enumerate() {
let buffer_slice = buffer.slice(..);
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
device.poll(wgpu::Maintain::Wait);
match buffer_future.await {
Err(e) => panic!("Unexpected failure: {}", e),
Ok(()) => {
let padded_buffer = buffer_slice.get_mapped_range();
let mut exact_buffer = Vec::with_capacity(
buffer_dimensions.unpadded_bytes_per_row * buffer_dimensions.height,
);
for y in 0..buffer_dimensions.height {
let row_beg = y * buffer_dimensions.padded_bytes_per_row;
let row_end = row_beg + buffer_dimensions.unpadded_bytes_per_row;
exact_buffer.extend_from_slice(&padded_buffer[row_beg..row_end]);
}
mip_buffers.push(MipBuffer {
buffer: exact_buffer,
dimensions: *buffer_dimensions,
level: level as u32,
});
}
}
}
Ok(mip_buffers)
}
pub fn checkerboard_r8(width: u32, height: u32, n: u32) -> Vec<u8> {
use std::iter;
(0..width * height)
.flat_map(|id| {
let x = id % width;
let y = id / height;
let v = (((x / n + y / n) % 2) * 255) as u8;
iter::once(v)
})
.collect()
}
#[doc(hidden)]
pub fn checkerboard_rgba8(width: u32, height: u32, n: u32) -> Vec<u8> {
use std::iter;
(0..width * height)
.flat_map(|id| {
let x = id % width;
let y = id / height;
let v = (((x / n + y / n) % 2) * 255) as u8;
iter::once(v)
.chain(iter::once(v))
.chain(iter::once(v))
.chain(iter::once(255))
})
.collect()
}
#[doc(hidden)]
pub fn checkerboard_rgba32f(width: u32, height: u32, n: u32) -> Vec<f32> {
use std::iter;
(0..width * height)
.flat_map(|id| {
let x = id % width;
let y = id / height;
let v = ((x / n + y / n) % 2) as f32;
iter::once(v)
.chain(iter::once(v))
.chain(iter::once(v))
.chain(iter::once(1.0))
})
.collect()
}
fn format_bytes_per_channel(format: &wgpu::TextureFormat) -> usize {
use wgpu::TextureFormat;
match format {
TextureFormat::R8Unorm => 1,
TextureFormat::R8Snorm => 1,
TextureFormat::R8Uint => 1,
TextureFormat::R8Sint => 1,
TextureFormat::R16Uint => 2,
TextureFormat::R16Sint => 2,
TextureFormat::R16Float => 2,
TextureFormat::Rg8Unorm => 2,
TextureFormat::Rg8Snorm => 2,
TextureFormat::Rg8Uint => 2,
TextureFormat::Rg8Sint => 2,
TextureFormat::R32Uint => 4,
TextureFormat::R32Sint => 4,
TextureFormat::R32Float => 4,
TextureFormat::Rg16Uint => 4,
TextureFormat::Rg16Sint => 4,
TextureFormat::Rg16Float => 4,
TextureFormat::Rgba8Unorm => 4,
TextureFormat::Rgba8Snorm => 4,
TextureFormat::Rgba8Uint => 4,
TextureFormat::Rgba8Sint => 4,
TextureFormat::Bgra8Unorm => 4,
TextureFormat::Bgra8UnormSrgb => 4,
TextureFormat::Rgba8UnormSrgb => 4,
TextureFormat::Rgb10a2Unorm => 4,
TextureFormat::Rg11b10Float => 4,
TextureFormat::Rg32Uint => 8,
TextureFormat::Rg32Sint => 8,
TextureFormat::Rg32Float => 8,
TextureFormat::Rgba16Uint => 8,
TextureFormat::Rgba16Sint => 8,
TextureFormat::Rgba16Float => 8,
TextureFormat::Rgba32Uint => 16,
TextureFormat::Rgba32Sint => 16,
TextureFormat::Rgba32Float => 16,
_ => unimplemented!(),
}
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) async fn wgpu_setup() -> (wgpu::Instance, wgpu::Adapter, wgpu::Device, wgpu::Queue) {
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
})
.await
.expect("Failed to find an appropiate adapter");
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
shader_validation: true,
},
None,
)
.await
.expect("Failed to create device");
(instance, adapter, device, queue)
}
#[doc(hidden)]
#[allow(dead_code)]
pub(crate) fn get_mip_extent(extent: &wgpu::Extent3d, level: u32) -> wgpu::Extent3d {
let mip_width = ((extent.width as f32) / (2u32.pow(level) as f32)).floor() as u32;
let mip_height = ((extent.height as f32) / (2u32.pow(level) as f32)).floor() as u32;
let mip_depth = ((extent.depth as f32) / (2u32.pow(level) as f32)).floor() as u32;
wgpu::Extent3d {
width: mip_width.max(1),
height: mip_height.max(1),
depth: mip_depth.max(1),
}
}