/*
* Copyright (C) Oscar Shrimpton 2020
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
//! A chunk of textures is an array of textures, the size of which is known at compile time.
//! This reduces the number of times we need to re-bind our descriptor sets
use crate::draw::texture::image::LoadableImage;
use hal::prelude::*;
use image::{Rgba, RgbaImage};
use core::mem::replace;
use std::ops::Deref;
use crate::{error, types::*};
use super::image::SampledImage;
use super::resolver::TextureResolver;
use log::debug;
use std::iter::Iterator;
use stockton_levels::traits::textures::Texture;
/// The size of a chunk. Needs to match up with the fragment shader
pub const CHUNK_SIZE: usize = 8;
/// An array of textures
pub struct TextureChunk {
pub(crate) descriptor_set: DescriptorSet,
sampled_images: Vec,
}
impl TextureChunk {
/// Create a new empty texture chunk
pub fn new_empty(
device: &mut Device,
adapter: &mut Adapter,
allocator: &mut DynamicAllocator,
command_queue: &mut CommandQueue,
command_pool: &mut CommandPool,
descriptor_set: DescriptorSet,
) -> Result {
let mut store = TextureChunk {
descriptor_set,
sampled_images: Vec::with_capacity(CHUNK_SIZE),
};
for i in 0..CHUNK_SIZE {
debug!("Putting a placeholder in slot {}", i);
store
.put_texture(
RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])),
i,
device,
adapter,
allocator,
command_queue,
command_pool,
)
.unwrap();
}
Ok(store)
}
/// Create a new texture chunk and load in the textures specified by `range` from `file` using `resolver`
/// Can error if the descriptor pool is too small or if a texture isn't found
pub fn new<'a, I, R: TextureResolver, T: LoadableImage>(
device: &mut Device,
adapter: &mut Adapter,
allocator: &mut DynamicAllocator,
command_queue: &mut CommandQueue,
command_pool: &mut CommandPool,
descriptor_set: DescriptorSet,
textures: I,
resolver: &mut R,
) -> Result
where
I: 'a + Iterator- ,
{
let mut store = TextureChunk {
descriptor_set,
sampled_images: Vec::with_capacity(CHUNK_SIZE),
};
let mut local_idx = 0;
debug!("Created descriptor set");
for tex in textures {
if let Some(img) = resolver.resolve(tex) {
store
.put_texture(
img,
local_idx,
device,
adapter,
allocator,
command_queue,
command_pool,
)
.unwrap();
} else {
// Texture not found. For now, tear everything down.
store.deactivate(device, allocator);
return Err(error::CreationError::BadDataError);
}
local_idx += 1;
}
// Pad out the end if needed
while local_idx < CHUNK_SIZE {
debug!("Putting a placeholder in slot {}", local_idx);
store
.put_texture(
RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])),
local_idx,
device,
adapter,
allocator,
command_queue,
command_pool,
)
.unwrap();
local_idx += 1;
}
Ok(store)
}
pub fn put_texture(
&mut self,
image: T,
idx: usize,
device: &mut Device,
adapter: &mut Adapter,
allocator: &mut DynamicAllocator,
command_queue: &mut CommandQueue,
command_pool: &mut CommandPool,
) -> Result<(), &'static str> {
// Load the image
let texture = SampledImage::load_into_new(
image,
device,
adapter,
allocator,
command_queue,
command_pool,
hal::format::Format::Rgba8Srgb, // TODO
hal::image::Usage::empty(),
)?;
// Write it to the descriptor set
unsafe {
use hal::image::Layout;
use hal::pso::{Descriptor, DescriptorSetWrite};
device.write_descriptor_sets(vec![
DescriptorSetWrite {
set: &self.descriptor_set,
binding: 0,
array_offset: idx,
descriptors: Some(Descriptor::Image(
texture.image.image_view.deref(),
Layout::ShaderReadOnlyOptimal,
)),
},
DescriptorSetWrite {
set: &self.descriptor_set,
binding: 1,
array_offset: idx,
descriptors: Some(Descriptor::Sampler(texture.sampler.deref())),
},
]);
};
// Store it so we can safely deactivate it when we need to
// Deactivate the old image if we need to
if idx < self.sampled_images.len() {
replace(&mut self.sampled_images[idx], texture).deactivate(device, allocator);
} else {
self.sampled_images.push(texture);
}
Ok(())
}
pub fn deactivate(mut self, device: &mut Device, allocator: &mut DynamicAllocator) {
for img in self.sampled_images.drain(..) {
img.deactivate(device, allocator);
}
}
}