use crossbeam::{
deque::{Injector, Steal},
sync::{Parker, Unparker},
};
use ilmenite::ImtWeight;
use image::{self, GenericImageView};
use misc::TmpImageViewAccess;
use ordered_float::OrderedFloat;
use parking_lot::{Condvar, Mutex};
use std::{
collections::HashMap,
fs::File,
io::Read,
path::PathBuf,
sync::{
atomic::{self, AtomicBool},
Arc,
},
thread,
time::Instant,
};
use vulkano::{
buffer::{cpu_access::CpuAccessibleBuffer, BufferAccess, BufferUsage as VkBufferUsage},
command_buffer::{AutoCommandBufferBuilder, CommandBuffer},
image::{
immutable::ImmutableImage, Dimensions as VkDimensions, ImageAccess,
ImageDimensions as VkImgDimensions, ImageUsage as VkImageUsage, ImageViewAccess,
StorageImage,
},
sampler::Sampler,
sync::GpuFuture,
};
use Basalt;
const PRINT_UPDATE_TIME: bool = false;
#[inline]
fn srgb_to_linear_d8(v: u8) -> u8 {
let mut f = v as f32 / 255.0;
if f < 0.04045 {
f /= 12.92;
} else {
f = ((f + 0.055) / 1.055).powf(2.4)
}
f = (f * 255.0).round();
if f > 255.0 {
f = 255.0;
} else if f < 0.0 {
f = 0.0;
}
f as u8
}
const CELL_WIDTH: u32 = 32;
const CELL_PAD: u32 = 5;
pub type AtlasImageID = u64;
pub type SubImageID = u64;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SubImageCacheID {
Path(PathBuf),
Url(String),
Glyph(String, ImtWeight, u16, OrderedFloat<f32>),
None,
}
impl SubImageCacheID {
pub fn path<P: Into<PathBuf>>(p: P) -> Self {
SubImageCacheID::Path(p.into())
}
pub fn url<U: Into<String>>(u: U) -> Self {
SubImageCacheID::Url(u.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Coords {
pub img_id: AtlasImageID,
pub sub_img_id: SubImageID,
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
impl Coords {
pub fn none() -> Self {
Coords {
img_id: 0,
sub_img_id: 0,
x: 0,
y: 0,
w: 0,
h: 0,
}
}
pub fn top_left(&self) -> (f32, f32) {
(self.x as f32, self.y as f32)
}
pub fn top_right(&self) -> (f32, f32) {
((self.x + self.w) as f32, self.y as f32)
}
pub fn bottom_left(&self) -> (f32, f32) {
(self.x as f32, (self.y + self.h) as f32)
}
pub fn bottom_right(&self) -> (f32, f32) {
((self.x + self.w) as f32, (self.y + self.h) as f32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ImageDims {
pub w: u32,
pub h: u32,
}
#[derive(Debug, Clone)]
pub enum ImageData {
D8(Vec<u8>),
#[doc(hidden)]
__Nonexhaustive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImageType {
LRGBA,
LRGB,
LMono,
SRGBA,
SRGB,
SMono,
Glyph,
YUV444,
}
impl ImageType {
pub fn components(&self) -> usize {
match self {
&ImageType::LRGBA => 4,
&ImageType::LRGB => 3,
&ImageType::LMono => 1,
&ImageType::SRGBA => 4,
&ImageType::SRGB => 3,
&ImageType::SMono => 1,
&ImageType::Glyph => 1,
&ImageType::YUV444 => 3,
}
}
}
pub struct Image {
ty: ImageType,
dims: ImageDims,
data: ImageData,
}
impl Image {
pub fn new(ty: ImageType, dims: ImageDims, mut data: ImageData) -> Result<Image, String> {
let expected_len = dims.w as usize * dims.h as usize * ty.components();
if expected_len == 0 {
return Err(format!("Image can't be empty"));
}
match &mut data {
&mut ImageData::D8(ref mut d) =>
if d.len() > expected_len {
d.truncate(expected_len);
} else if d.len() < expected_len {
return Err(format!("Data length doesn't match the provided dimensions."));
},
_ => unreachable!(),
}
Ok(Image {
ty,
dims,
data,
})
}
pub fn into_data(self) -> ImageData {
self.data
}
pub fn to_lrgba(self) -> Self {
if let ImageData::D8(data) = self.data {
let mut lrgba = Vec::with_capacity(data.len() / self.ty.components() * 4);
match self.ty {
ImageType::LRGBA => lrgba = data,
ImageType::LRGB =>
for v in data {
lrgba.push(v);
if lrgba.len() % 4 == 2 {
lrgba.push(255);
}
},
ImageType::LMono =>
for v in data {
lrgba.push(v);
lrgba.push(v);
lrgba.push(v);
lrgba.push(255);
},
ImageType::SMono =>
for mut v in data {
v = srgb_to_linear_d8(v);
lrgba.push(v);
lrgba.push(v);
lrgba.push(v);
lrgba.push(255);
},
ImageType::SRGBA =>
for v in data {
lrgba.push(srgb_to_linear_d8(v));
},
ImageType::SRGB =>
for v in data {
lrgba.push(srgb_to_linear_d8(v));
if lrgba.len() % 4 == 2 {
lrgba.push(255);
}
},
ImageType::Glyph =>
for v in data {
lrgba.push(0);
lrgba.push(0);
lrgba.push(0);
lrgba.push(srgb_to_linear_d8(v));
},
ImageType::YUV444 =>
for chunk in data.chunks_exact(3) {
let mut components = [
chunk[0] as f32 + (1.402 * (chunk[2] as f32 - 128.0)),
chunk[0] as f32 + (0.344 * (chunk[1] as f32 - 128.0))
- (0.714 * (chunk[2] as f32 - 128.0)),
chunk[0] as f32 + (1.772 * (chunk[1] as f32 - 128.0)),
];
for v in &mut components {
*v = ((*v + (0.055 * 255.0)) / 1.055).powf(2.4).round();
if *v > 255.0 {
*v = 255.0;
} else if *v < 0.0 {
*v = 0.0;
}
}
for v in &components {
lrgba.push(*v as u8);
}
lrgba.push(255);
},
}
Image {
ty: ImageType::LRGBA,
dims: self.dims,
data: ImageData::D8(lrgba),
}
} else {
unreachable!()
}
}
}
enum Command {
Upload(Arc<CommandResponse<Result<Coords, String>>>, SubImageCacheID, Image),
CacheIDLookup(Arc<CommandResponse<Option<Coords>>>, SubImageCacheID),
BatchCacheIDLookup(Arc<CommandResponse<Vec<Option<Coords>>>>, Vec<SubImageCacheID>),
Delete(SubImageID),
DeleteCache(SubImageCacheID),
}
struct CommandResponse<T> {
response: Mutex<Option<T>>,
condvar: Condvar,
}
impl<T> CommandResponse<T> {
fn new() -> Arc<Self> {
Arc::new(CommandResponse {
response: Mutex::new(None),
condvar: Condvar::new(),
})
}
fn respond(&self, val: T) {
*self.response.lock() = Some(val);
self.condvar.notify_one();
}
fn wait_for_response(&self) -> T {
let mut response = self.response.lock();
while response.is_none() {
self.condvar.wait(&mut response);
}
response.take().unwrap()
}
}
pub struct Atlas {
basalt: Arc<Basalt>,
cmd_queue: Injector<Command>,
empty_image: Arc<dyn ImageViewAccess + Send + Sync>,
default_sampler: Arc<Sampler>,
unparker: Unparker,
image_views: Mutex<
Option<(Instant, Arc<HashMap<AtlasImageID, Arc<dyn ImageViewAccess + Send + Sync>>>)>,
>,
}
impl Atlas {
pub fn new(basalt: Arc<Basalt>) -> Arc<Self> {
let default_sampler = Sampler::unnormalized(
basalt.device(),
vulkano::sampler::Filter::Linear,
vulkano::sampler::UnnormalizedSamplerAddressMode::ClampToBorder(
vulkano::sampler::BorderColor::IntTransparentBlack,
),
vulkano::sampler::UnnormalizedSamplerAddressMode::ClampToBorder(
vulkano::sampler::BorderColor::IntTransparentBlack,
),
)
.unwrap();
let empty_image = ImmutableImage::<vulkano::format::R8G8B8A8Unorm>::from_iter(
vec![255, 255, 255, 255].into_iter(),
VkDimensions::Dim2d {
width: 1,
height: 1,
},
vulkano::format::R8G8B8A8Unorm,
basalt.compute_queue.clone(),
)
.unwrap()
.0;
let parker = Parker::new();
let unparker = parker.unparker().clone();
let atlas_ret = Arc::new(Atlas {
basalt,
unparker,
default_sampler,
empty_image,
cmd_queue: Injector::new(),
image_views: Mutex::new(None),
});
let atlas = atlas_ret.clone();
thread::spawn(move || {
let mut iter_start;
let mut atlas_images: Vec<AtlasImage> = Vec::new();
let mut sub_img_id_count = 1;
let mut cached_map = HashMap::new();
let mut execute = false;
loop {
iter_start = Instant::now();
let mut cmds = Vec::new();
let mut got_cmd = false;
loop {
let cmd = match atlas.cmd_queue.steal() {
Steal::Empty => break,
Steal::Retry => continue,
Steal::Success(cmd) => cmd,
};
got_cmd = true;
match cmd {
Command::Upload(response, cache_id, up_image) => {
let mut space_op = None;
for (i, atlas_image) in atlas_images.iter().enumerate() {
if let Some(region) = atlas_image.find_space_for(&up_image.dims)
{
space_op = Some((i + 1, region));
break;
}
}
if space_op.is_none() {
let atlas_image = AtlasImage::new(atlas.basalt.clone());
match atlas_image.find_space_for(&up_image.dims) {
Some(region) => {
space_op = Some((atlas_images.len() + 1, region));
},
None => {
response.respond(Err(format!(
"Image to big to fit in atlas."
)));
continue;
},
}
atlas_images.push(atlas_image);
}
let (atlas_image_i, region) = space_op.unwrap();
let sub_img_id = sub_img_id_count;
sub_img_id_count += 1;
let coords =
region.coords(atlas_image_i as u64, sub_img_id, &up_image.dims);
if cache_id != SubImageCacheID::None {
cached_map.insert(cache_id.clone(), coords);
}
response.respond(Ok(coords));
atlas_images[atlas_image_i - 1]
.insert(®ion, sub_img_id, coords, up_image);
},
c => cmds.push(c),
}
}
if !got_cmd && !execute {
parker.park();
continue;
}
for cmd in cmds {
match cmd {
Command::Upload(..) => unreachable!(),
Command::Delete(_sub_img_id) => (),
Command::DeleteCache(_sub_img_cache_id) => (),
Command::CacheIDLookup(response, cache_id) => {
response.respond(cached_map.get(&cache_id).cloned());
},
Command::BatchCacheIDLookup(response, cache_ids) => {
response.respond(
cache_ids
.into_iter()
.map(|cache_id| cached_map.get(&cache_id).cloned())
.collect(),
);
},
}
}
let mut cmd_buf = AutoCommandBufferBuilder::new(
atlas.basalt.device(),
atlas.basalt.compute_queue_ref().family(),
)
.unwrap();
execute = false;
let mut sizes = Vec::new();
for atlas_image in &mut atlas_images {
let res = atlas_image.update(cmd_buf);
cmd_buf = res.0;
sizes.push((res.2, res.3));
if res.1 {
execute = res.1;
}
}
if execute {
drop(
cmd_buf
.build()
.unwrap()
.execute(atlas.basalt.compute_queue())
.unwrap()
.then_signal_fence_and_flush()
.unwrap(),
);
let mut draw_map = HashMap::new();
for (i, atlas_image) in atlas_images.iter_mut().enumerate() {
if let Some(tmp_img) = atlas_image.complete_update() {
draw_map.insert(
(i + 1) as u64,
Arc::new(tmp_img) as Arc<dyn ImageViewAccess + Send + Sync>,
);
}
}
*atlas.image_views.lock() = Some((Instant::now(), Arc::new(draw_map)));
}
if PRINT_UPDATE_TIME && execute {
let mut out = format!(
"Atlas Updated in {:.1} ms. ",
iter_start.elapsed().as_micros() as f64 / 1000.0
);
for (i, (w, h)) in sizes.into_iter().enumerate() {
out.push_str(format!("{}:{}x{} ", i + 1, w, h).as_str());
}
out.pop();
println!("{}", out);
}
}
});
atlas_ret
}
pub fn image_views(
&self,
) -> Option<(Instant, Arc<HashMap<AtlasImageID, Arc<dyn ImageViewAccess + Send + Sync>>>)> {
self.image_views.lock().clone()
}
pub fn empty_image(&self) -> Arc<dyn ImageViewAccess + Send + Sync> {
self.empty_image.clone()
}
pub fn default_sampler(&self) -> Arc<Sampler> {
self.default_sampler.clone()
}
pub fn delete_sub_image(&self, sub_img_id: SubImageID) {
self.cmd_queue.push(Command::Delete(sub_img_id));
}
pub fn delete_sub_cache_image(&self, sub_img_cache_id: SubImageCacheID) {
self.cmd_queue.push(Command::DeleteCache(sub_img_cache_id));
}
pub fn cache_coords(&self, cache_id: SubImageCacheID) -> Option<Coords> {
let response = CommandResponse::new();
self.cmd_queue.push(Command::CacheIDLookup(response.clone(), cache_id));
self.unparker.unpark();
response.wait_for_response()
}
pub fn batch_cache_coords(&self, cache_ids: Vec<SubImageCacheID>) -> Vec<Option<Coords>> {
let response = CommandResponse::new();
self.cmd_queue.push(Command::BatchCacheIDLookup(response.clone(), cache_ids));
self.unparker.unpark();
response.wait_for_response()
}
pub fn load_image(
&self,
cache_id: SubImageCacheID,
mut image: Image,
) -> Result<Coords, String> {
image = image.to_lrgba();
let response = CommandResponse::new();
self.cmd_queue.push(Command::Upload(response.clone(), cache_id, image));
self.unparker.unpark();
response.wait_for_response()
}
pub fn load_image_from_bytes(
&self,
cache_id: SubImageCacheID,
bytes: Vec<u8>,
) -> Result<Coords, String> {
let format = match image::guess_format(bytes.as_slice()) {
Ok(ok) => ok,
Err(e) => return Err(format!("Failed to guess image type for data: {}", e)),
};
let (w, h, data) = match image::load_from_memory(bytes.as_slice()) {
Ok(image) => (image.width(), image.height(), image.to_rgba().into_vec()),
Err(e) => return Err(format!("Failed to read image: {}", e)),
};
let image_type = match format {
image::ImageFormat::JPEG => ImageType::SRGBA,
_ => ImageType::LRGBA,
};
let image = Image::new(
image_type,
ImageDims {
w,
h,
},
ImageData::D8(data),
)
.map_err(|e| format!("Invalid Image: {}", e))?;
self.load_image(cache_id, image)
}
pub fn load_image_from_path<P: Into<PathBuf>>(&self, path: P) -> Result<Coords, String> {
let path_buf = path.into();
let cache_id = SubImageCacheID::Path(path_buf.clone());
if let Some(coords) = self.cache_coords(cache_id.clone()) {
return Ok(coords);
}
let mut handle = match File::open(path_buf) {
Ok(ok) => ok,
Err(e) => return Err(format!("Failed to open file: {}", e)),
};
let mut bytes = Vec::new();
if let Err(e) = handle.read_to_end(&mut bytes) {
return Err(format!("Failed to read file: {}", e));
}
self.load_image_from_bytes(cache_id, bytes)
}
pub fn load_image_from_url<U: AsRef<str>>(
self: &Arc<Self>,
url: U,
) -> Result<Coords, String> {
let cache_id = SubImageCacheID::Url(url.as_ref().to_string());
if let Some(coords) = self.cache_coords(cache_id.clone()) {
return Ok(coords);
}
let bytes = match ::misc::http::get_bytes(&url) {
Ok(ok) => ok,
Err(e) => return Err(format!("Failed to retreive url data: {}", e)),
};
self.load_image_from_bytes(cache_id, bytes)
}
}
struct Region {
x: usize,
y: usize,
w: usize,
h: usize,
}
impl Region {
fn coords(&self, img_id: AtlasImageID, sub_img_id: SubImageID, dims: &ImageDims) -> Coords {
Coords {
img_id,
sub_img_id,
x: (self.x as u32 * CELL_WIDTH)
+ (self.x.checked_sub(1).unwrap_or(0) as u32 * CELL_PAD)
+ CELL_PAD,
y: (self.y as u32 * CELL_WIDTH)
+ (self.y.checked_sub(1).unwrap_or(0) as u32 * CELL_PAD)
+ CELL_PAD,
w: dims.w,
h: dims.h,
}
}
}
struct SubImage {
coords: Coords,
img: Image,
}
struct AtlasImage {
basalt: Arc<Basalt>,
active: Option<usize>,
update: Option<usize>,
sto_imgs: Vec<Arc<dyn ImageAccess + Send + Sync>>,
sto_imgs_view: Vec<Arc<dyn ImageViewAccess + Send + Sync>>,
sub_imgs: HashMap<SubImageID, SubImage>,
sto_leases: Vec<Vec<Arc<AtomicBool>>>,
con_sub_img: Vec<Vec<SubImageID>>,
alloc_cell_w: usize,
alloc: Vec<Vec<Option<SubImageID>>>,
}
impl AtlasImage {
fn new(basalt: Arc<Basalt>) -> Self {
let max_img_w = basalt.limits().max_image_dimension_2d as f32 + CELL_PAD as f32;
let alloc_cell_w = (max_img_w / (CELL_WIDTH + CELL_PAD) as f32).floor() as usize;
let mut alloc = Vec::with_capacity(alloc_cell_w);
alloc.resize_with(alloc_cell_w, || {
let mut out = Vec::with_capacity(alloc_cell_w);
out.resize(alloc_cell_w, None);
out
});
AtlasImage {
basalt,
alloc,
alloc_cell_w,
active: None,
update: None,
sto_imgs: Vec::new(),
sto_imgs_view: Vec::new(),
sto_leases: Vec::new(),
sub_imgs: HashMap::new(),
con_sub_img: Vec::new(),
}
}
fn complete_update(&mut self) -> Option<TmpImageViewAccess> {
let img_i = match self.update.take() {
Some(img_i) => {
self.active = Some(img_i);
img_i
},
None => *self.active.as_ref()?,
};
let (tmp_img, abool) = TmpImageViewAccess::new_abool(self.sto_imgs_view[img_i].clone());
self.sto_leases[img_i].push(abool);
Some(tmp_img)
}
fn update(
&mut self,
mut cmd_buf: AutoCommandBufferBuilder,
) -> (AutoCommandBufferBuilder, bool, u32, u32) {
self.update = None;
let mut found_op = None;
let (min_img_w, min_img_h) = self.minium_size();
let mut cur_img_w = 0;
let mut cur_img_h = 0;
let mut resize = false;
for (i, sto_img) in self.sto_imgs.iter().enumerate() {
self.sto_leases[i].retain(|v| v.load(atomic::Ordering::Relaxed));
if found_op.is_none() && self.sto_leases[i].is_empty() {
if let VkImgDimensions::Dim2d {
width,
height,
..
} = sto_img.dimensions()
{
self.update = Some(i);
found_op = Some((i, sto_img.clone()));
cur_img_w = width;
cur_img_h = height;
resize = width < min_img_w || height < min_img_h;
} else {
unreachable!()
}
}
}
if found_op.is_none() && self.sto_imgs.len() > 3 {
return (cmd_buf, false, cur_img_w, cur_img_h);
}
if found_op.is_none() || resize {
let img_i = match found_op.as_ref() {
Some((img_i, _)) => *img_i,
None => self.sto_imgs.len(),
};
let image = StorageImage::<vulkano::format::A8B8G8R8SrgbPack32>::with_usage(
self.basalt.device(),
VkDimensions::Dim2d {
width: min_img_w,
height: min_img_h,
},
vulkano::format::A8B8G8R8SrgbPack32,
VkImageUsage {
transfer_source: true,
transfer_destination: true,
sampled: true,
..VkImageUsage::none()
},
vec![self.basalt.compute_queue_ref().family()],
)
.unwrap();
if img_i < self.sto_imgs.len() {
let r_w = min_img_w - cur_img_w;
let r_h = cur_img_h;
let mut r_zeros = Vec::new();
r_zeros.resize((r_w * r_h * 4) as usize, 0);
let r_buf = CpuAccessibleBuffer::from_iter(
self.basalt.device(),
VkBufferUsage {
transfer_source: true,
..VkBufferUsage::none()
},
false,
r_zeros.into_iter(),
)
.unwrap();
cmd_buf
.copy_buffer_to_image_dimensions(
r_buf,
image.clone(),
[cur_img_w, 0, 0],
[r_w, r_h, 0],
0,
1,
0,
)
.unwrap();
let b_w = min_img_w;
let b_h = min_img_h - cur_img_h;
let mut b_zeros = Vec::new();
b_zeros.resize((b_w * b_h * 4) as usize, 0);
let b_buf = CpuAccessibleBuffer::from_iter(
self.basalt.device(),
VkBufferUsage {
transfer_source: true,
..VkBufferUsage::none()
},
false,
b_zeros.into_iter(),
)
.unwrap();
cmd_buf
.copy_buffer_to_image_dimensions(
b_buf,
image.clone(),
[0, cur_img_h, 0],
[b_w, b_h, 0],
0,
1,
0,
)
.unwrap();
cmd_buf
.copy_image(
self.sto_imgs[img_i].clone(),
[0, 0, 0],
0,
0,
image.clone(),
[0, 0, 0],
0,
0,
[cur_img_w, cur_img_h, 1],
1,
)
.unwrap();
self.sto_imgs[img_i] = image.clone();
self.sto_imgs_view[img_i] = image.clone();
self.sto_leases[img_i].clear();
found_op = Some((img_i, image));
cur_img_w = min_img_w;
cur_img_h = min_img_h;
} else {
cmd_buf.clear_color_image(image.clone(), [0_32; 4].into()).unwrap();
self.sto_imgs.push(image.clone());
self.sto_imgs_view.push(image.clone());
self.con_sub_img.push(Vec::new());
self.sto_leases.push(Vec::new());
found_op = Some((img_i, image));
self.update = Some(img_i);
cur_img_w = min_img_w;
cur_img_h = min_img_h;
}
}
let (img_i, sto_img) = found_op.unwrap();
let mut upload_data = Vec::new();
let mut copy_cmds = Vec::new();
for (sub_img_id, sub_img) in &self.sub_imgs {
if !self.con_sub_img[img_i].contains(sub_img_id) {
if let ImageData::D8(sub_img_data) = &sub_img.img.data {
assert!(ImageType::LRGBA == sub_img.img.ty);
assert!(!sub_img_data.is_empty());
let s = upload_data.len();
upload_data.extend_from_slice(&sub_img_data);
copy_cmds.push((
s,
upload_data.len(),
sub_img.coords.x,
sub_img.coords.y,
sub_img.coords.w,
sub_img.coords.h,
));
self.con_sub_img[img_i].push(*sub_img_id);
} else {
unreachable!()
}
}
}
if copy_cmds.is_empty() {
self.update = None;
return (cmd_buf, false, cur_img_w, cur_img_h);
}
let upload_buf = CpuAccessibleBuffer::from_iter(
self.basalt.device(),
VkBufferUsage {
transfer_source: true,
..VkBufferUsage::none()
},
false,
upload_data.into_iter(),
)
.unwrap();
for (s, e, x, y, w, h) in copy_cmds {
cmd_buf
.copy_buffer_to_image_dimensions(
upload_buf.clone().into_buffer_slice().slice(s..e).unwrap(),
sto_img.clone(),
[x, y, 0],
[w, h, 0],
0,
1,
0,
)
.unwrap();
}
(cmd_buf, true, cur_img_w, cur_img_h)
}
fn minium_size(&self) -> (u32, u32) {
let mut min_x = 1;
let mut min_y = 1;
for sub_img in self.sub_imgs.values() {
let x = sub_img.coords.x + sub_img.coords.w;
let y = sub_img.coords.y + sub_img.coords.h;
if x > min_x {
min_x = x;
}
if y > min_y {
min_y = y;
}
}
min_x += CELL_PAD;
min_y += CELL_PAD;
(min_x, min_y)
}
fn find_space_for(&self, dims: &ImageDims) -> Option<Region> {
let w = (dims.w as f32 / CELL_WIDTH as f32).ceil() as usize;
let h = (dims.h as f32 / CELL_WIDTH as f32).ceil() as usize;
let mut cell_pos = None;
for i in 0..self.alloc_cell_w {
for j in 0..self.alloc_cell_w {
let mut fits = true;
for k in 0..w {
for l in 0..h {
match self.alloc.get(i + k).and_then(|xarr| xarr.get(j + l)) {
Some(cell) =>
if cell.is_some() {
fits = false;
break;
},
None => {
fits = false;
break;
},
}
}
if !fits {
break;
}
}
if fits {
cell_pos = Some((i, j));
break;
}
}
if cell_pos.is_some() {
break;
}
}
let (x, y) = cell_pos?;
Some(Region {
x,
y,
w,
h,
})
}
fn insert(&mut self, region: &Region, sub_img_id: SubImageID, coords: Coords, img: Image) {
for x in region.x..(region.x + region.w) {
for y in region.y..(region.y + region.h) {
self.alloc[x][y] = Some(sub_img_id);
}
}
self.sub_imgs.insert(sub_img_id, SubImage {
coords,
img,
});
}
}