aboutsummaryrefslogtreecommitdiff
path: root/stockton-render/src/draw/texture
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:21 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:21 +0100
commit9f6b7a10e7e00edf6ab50a9cf162b377d84a43f6 (patch)
treefe4ebd024c59376c425073b4fa747fa70824d171 /stockton-render/src/draw/texture
parent26c325640df7ab8b9ea4a6c3fd5166f7df7f80ff (diff)
feat(render) WIP: textures, using a fixed size texture array
currently using an 8 long texture array with seperate samplers. also move to a trait for resolving textures from the bsp file rather than adding them manually. currently stuck with one static implementation though. this means all textures in the file are loaded when the map is. not currently drawing with anything other than the first texture array, so files with >8 textures will fail.
Diffstat (limited to 'stockton-render/src/draw/texture')
-rw-r--r--stockton-render/src/draw/texture/chunk.rs152
-rw-r--r--stockton-render/src/draw/texture/image.rs264
-rw-r--r--stockton-render/src/draw/texture/loader.rs161
-rw-r--r--stockton-render/src/draw/texture/mod.rs22
-rw-r--r--stockton-render/src/draw/texture/resolver.rs54
5 files changed, 653 insertions, 0 deletions
diff --git a/stockton-render/src/draw/texture/chunk.rs b/stockton-render/src/draw/texture/chunk.rs
new file mode 100644
index 0000000..9356274
--- /dev/null
+++ b/stockton-render/src/draw/texture/chunk.rs
@@ -0,0 +1,152 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// 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 <http://www.gnu.org/licenses/>.
+
+//! 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 image::{Rgba, RgbaImage};
+use hal::prelude::*;
+
+use core::{
+ mem::{replace}
+};
+use std::ops::{Range, Deref};
+
+use crate::{
+ types::*,
+ error
+};
+
+use log::debug;
+use super::resolver::TextureResolver;
+use super::image::LoadedImage;
+use stockton_levels::prelude::*;
+
+/// 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,
+ loaded_images: Vec<LoadedImage>,
+}
+
+impl TextureChunk {
+ pub fn new<T: HasTextures, R: TextureResolver>(device: &mut Device,
+ adapter: &mut Adapter,
+ command_queue: &mut CommandQueue,
+ command_pool: &mut CommandPool,
+ pool: &mut DescriptorPool,
+ layout: &DescriptorSetLayout,
+ file: &T, range: Range<u32>,
+ resolver: &mut R) -> Result<TextureChunk, error::CreationError> {
+
+ let descriptor_set = unsafe {
+ pool.allocate_set(&layout).map_err(|e| {
+ println!("{:?}", e);
+ error::CreationError::OutOfMemoryError
+ })?
+ };
+
+ let mut store = TextureChunk {
+ descriptor_set: descriptor_set,
+ loaded_images: Vec::with_capacity(CHUNK_SIZE),
+ };
+
+ let mut local_idx = 0;
+
+ debug!("Created descriptor set");
+ for tex_idx in range {
+ debug!("Loading tex {}", local_idx + 1);
+ let tex = file.get_texture(tex_idx);
+ let img = resolver.resolve(tex);
+ store.put_texture(img, local_idx,
+ device, adapter,
+ command_queue, command_pool).unwrap();
+
+ 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,
+ command_queue, command_pool).unwrap();
+
+ local_idx += 1;
+ }
+
+ Ok(store)
+ }
+
+
+ pub fn put_texture(&mut self, image: RgbaImage,
+ idx: usize,
+ device: &mut Device,
+ adapter: &mut Adapter,
+ command_queue: &mut CommandQueue,
+ command_pool: &mut CommandPool) -> Result<(), &'static str>{
+
+ // Load the image
+ let texture = LoadedImage::load(
+ image,
+ device,
+ adapter,
+ command_queue,
+ command_pool,
+ )?;
+
+ // Write it to the descriptor set
+ unsafe {
+ use hal::pso::{DescriptorSetWrite, Descriptor};
+ use hal::image::Layout;
+
+ device.write_descriptor_sets(vec![
+ DescriptorSetWrite {
+ set: &self.descriptor_set,
+ binding: 0,
+ array_offset: idx,
+ descriptors: Some(Descriptor::Image(
+ texture.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.loaded_images.len() {
+ replace(&mut self.loaded_images[idx], texture).deactivate(device);
+ } else {
+ self.loaded_images.push(texture);
+ }
+
+ Ok(())
+ }
+
+ pub fn deactivate(mut self, device: &mut Device) -> () {
+ for img in self.loaded_images.drain(..) {
+ img.deactivate(device);
+ }
+ }
+} \ No newline at end of file
diff --git a/stockton-render/src/draw/texture/image.rs b/stockton-render/src/draw/texture/image.rs
new file mode 100644
index 0000000..fec84a2
--- /dev/null
+++ b/stockton-render/src/draw/texture/image.rs
@@ -0,0 +1,264 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// 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 <http://www.gnu.org/licenses/>.
+
+use core::ptr::copy_nonoverlapping;
+use std::iter::once;
+use core::mem::size_of;
+use image::RgbaImage;
+use draw::buffer::create_buffer;
+use core::mem::ManuallyDrop;
+use hal::{
+ MemoryTypeId,
+ buffer::Usage as BufUsage,
+ format::{Format, Swizzle, Aspects},
+ image::{ViewKind, SubresourceRange},
+ queue::Submission,
+ memory::{Properties as MemProperties, Dependencies as MemDependencies, Segment},
+ prelude::*,
+};
+use std::convert::TryInto;
+use crate::types::*;
+
+/// The size of each pixel in an image
+const PIXEL_SIZE: usize = size_of::<image::Rgba<u8>>();
+
+
+/// Holds an image that's loaded into GPU memory and can be sampled from
+pub struct LoadedImage {
+ image: ManuallyDrop<Image>,
+ pub image_view: ManuallyDrop<ImageView>,
+ pub sampler: ManuallyDrop<Sampler>,
+ memory: ManuallyDrop<Memory>
+}
+
+impl LoadedImage {
+ /// Load the given image into a new buffer
+ pub fn load(img: RgbaImage, device: &mut Device, adapter: &Adapter,
+ command_queue: &mut CommandQueue,
+ command_pool: &mut CommandPool) -> Result<LoadedImage, &'static str> {
+ // Round up the size to align properly
+ let initial_row_size = PIXEL_SIZE * (img.width() as usize);
+ let limits = adapter.physical_device.limits();
+ let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1;
+
+ let row_size = ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize;
+ debug_assert!(row_size as usize >= initial_row_size);
+
+ let total_size = (row_size * img.height() as usize) as u64;
+
+ // Make a staging buffer
+ let (staging_buffer, staging_memory) = create_buffer(device, adapter, BufUsage::TRANSFER_SRC, MemProperties::CPU_VISIBLE, total_size)
+ .map_err(|_| "Couldn't create staging buffer")?;
+
+ // Copy everything into it
+ unsafe {
+ let mapped_memory: *mut u8 = device.map_memory(&staging_memory, Segment::ALL).map_err(|_| "Couldn't map buffer memory")?;
+
+ for y in 0..img.height() as usize {
+ let row = &(*img)[y * initial_row_size..(y + 1) * initial_row_size];
+ let dest_base: isize = (y * row_size).try_into().unwrap();
+
+ copy_nonoverlapping(row.as_ptr(), mapped_memory.offset(dest_base), row.len());
+ }
+ device.flush_mapped_memory_ranges(once((&staging_memory, Segment::ALL))).map_err(|_| "Couldn't write buffer memory")?;
+ device.unmap_memory(&staging_memory);
+ }
+
+ // Make the image
+ let mut image_ref = unsafe {
+ use hal::image::{Kind, Tiling, Usage, ViewCapabilities};
+
+ device.create_image(
+ Kind::D2(img.width(), img.height(), 1, 1),
+ 1,
+ Format::Rgba8Srgb,
+ Tiling::Optimal,
+ Usage::TRANSFER_DST | Usage::SAMPLED,
+ ViewCapabilities::empty()
+ )
+ }.map_err(|_| "Couldn't create image")?;
+
+ // Allocate memory
+ let memory = unsafe {
+ let requirements = device.get_image_requirements(&image_ref);
+
+ let memory_type_id = adapter.physical_device
+ .memory_properties().memory_types
+ .iter().enumerate()
+ .find(|&(id, memory_type)| {
+ requirements.type_mask & (1 << id) != 0 && memory_type.properties.contains(MemProperties::DEVICE_LOCAL)
+ })
+ .map(|(id, _)| MemoryTypeId(id))
+ .ok_or("Couldn't find a memory type for image memory")?;
+
+ let memory = device
+ .allocate_memory(memory_type_id, requirements.size)
+ .map_err(|_| "Couldn't allocate image memory")?;
+
+ device.bind_image_memory(&memory, 0, &mut image_ref)
+ .map_err(|_| "Couldn't bind memory to image")?;
+
+ Ok(memory)
+ }?;
+
+ // Copy from staging to image memory
+ let buf = unsafe {
+ use hal::command::{CommandBufferFlags, BufferImageCopy};
+ use hal::pso::PipelineStage;
+ use hal::memory::Barrier;
+ use hal::image::{Access, Layout, SubresourceLayers, Offset, Extent};
+
+ // Get a command buffer
+ let mut buf = command_pool.allocate_one(hal::command::Level::Primary);
+ buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT);
+
+ // Setup the layout of our image for copying
+ let image_barrier = Barrier::Image {
+ states: (Access::empty(), Layout::Undefined)
+ ..(
+ Access::TRANSFER_WRITE,
+ Layout::TransferDstOptimal,
+ ),
+ target: &image_ref,
+ families: None,
+ range: SubresourceRange {
+ aspects: Aspects::COLOR,
+ levels: 0..1,
+ layers: 0..1,
+ },
+ };
+ buf.pipeline_barrier(
+ PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER,
+ MemDependencies::empty(),
+ &[image_barrier],
+ );
+
+ // Copy from buffer to image
+ buf.copy_buffer_to_image(&staging_buffer, &image_ref,
+ Layout::TransferDstOptimal, &[
+ BufferImageCopy {
+ buffer_offset: 0,
+ buffer_width: (row_size / PIXEL_SIZE) as u32,
+ buffer_height: img.height(),
+ image_layers: SubresourceLayers {
+ aspects: Aspects::COLOR,
+ level: 0,
+ layers: 0..1
+ },
+ image_offset: Offset {
+ x: 0, y: 0, z: 0
+ },
+ image_extent: Extent {
+ width: img.width(),
+ height: img.height(),
+ depth: 1
+ }
+ }
+ ]);
+
+ // Setup the layout of our image for shaders
+ let image_barrier = Barrier::Image {
+ states: (
+ Access::TRANSFER_WRITE,
+ Layout::TransferDstOptimal,
+ )..(
+ Access::SHADER_READ,
+ Layout::ShaderReadOnlyOptimal,
+ ),
+ target: &image_ref,
+ families: None,
+ range: SubresourceRange {
+ aspects: Aspects::COLOR,
+ levels: 0..1,
+ layers: 0..1,
+ },
+ };
+
+ buf.pipeline_barrier(
+ PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER,
+ MemDependencies::empty(),
+ &[image_barrier],
+ );
+
+ buf.finish();
+
+ buf
+ };
+
+ // Submit our commands and wait for them to finish
+ unsafe {
+ let setup_finished = device.create_fence(false).unwrap();
+ command_queue.submit::<_, _, Semaphore, _, _>(Submission {
+ command_buffers: &[&buf],
+ wait_semaphores: std::iter::empty::<_>(),
+ signal_semaphores: std::iter::empty::<_>()
+ }, Some(&setup_finished));
+
+ device
+ .wait_for_fence(&setup_finished, core::u64::MAX).unwrap();
+ device.destroy_fence(setup_finished);
+ };
+
+ // Clean up temp resources
+ unsafe {
+ command_pool.free(once(buf));
+
+ device.free_memory(staging_memory);
+ device.destroy_buffer(staging_buffer);
+ }
+
+ // Create ImageView and sampler
+ let image_view = unsafe { device.create_image_view(
+ &image_ref,
+ ViewKind::D2,
+ Format::Rgba8Srgb,
+ Swizzle::NO,
+ SubresourceRange {
+ aspects: Aspects::COLOR,
+ levels: 0..1,
+ layers: 0..1,
+ },
+ )}.map_err(|_| "Couldn't create the image view!")?;
+
+ let sampler = unsafe {
+ use hal::image::{SamplerDesc, Filter, WrapMode};
+
+ device.create_sampler(&SamplerDesc::new(
+ Filter::Nearest,
+ WrapMode::Tile,
+ ))
+ }.map_err(|_| "Couldn't create the sampler!")?;
+
+ Ok(LoadedImage {
+ image: ManuallyDrop::new(image_ref),
+ image_view: ManuallyDrop::new(image_view),
+ sampler: ManuallyDrop::new(sampler),
+ memory: ManuallyDrop::new(memory)
+ })
+ }
+
+ /// Properly frees/destroys all the objects in this struct
+ /// Dropping without doing this is a bad idea
+ pub fn deactivate(self, device: &Device) -> () {
+ unsafe {
+ use core::ptr::read;
+
+ device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler)));
+ device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view)));
+ device.destroy_image(ManuallyDrop::into_inner(read(&self.image)));
+ device.free_memory(ManuallyDrop::into_inner(read(&self.memory)));
+ }
+ }
+} \ No newline at end of file
diff --git a/stockton-render/src/draw/texture/loader.rs b/stockton-render/src/draw/texture/loader.rs
new file mode 100644
index 0000000..017030f
--- /dev/null
+++ b/stockton-render/src/draw/texture/loader.rs
@@ -0,0 +1,161 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// 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 <http://www.gnu.org/licenses/>.
+//! Deals with loading textures into GPU memory
+
+use std::path::Path;
+use draw::texture::resolver::BasicFSResolver;
+use draw::texture::chunk::CHUNK_SIZE;
+use core::mem::{ManuallyDrop};
+use super::chunk::TextureChunk;
+
+use log::debug;
+
+use hal::{
+ prelude::*,
+};
+
+use stockton_levels::prelude::*;
+
+use crate::error;
+use crate::types::*;
+
+/// Stores all loaded textures in GPU memory.
+/// When rendering, the descriptor sets are bound to the buffer
+/// The descriptor set layout should have the same count of textures as this does.
+/// Note that it's possible not all descriptors are actually initialised images
+pub struct TextureStore {
+ descriptor_pool: ManuallyDrop<DescriptorPool>,
+ pub(crate) descriptor_set_layout: ManuallyDrop<DescriptorSetLayout>,
+ chunks: Box<[TextureChunk]>
+}
+
+impl TextureStore {
+ pub fn new<T: HasTextures>(device: &mut Device,
+ adapter: &mut Adapter,
+ command_queue: &mut CommandQueue,
+ command_pool: &mut CommandPool, file: &T) -> Result<TextureStore, error::CreationError> {
+ // Figure out how many textures in this file
+ let size = file.textures_iter().count();
+ let num_chunks = {
+ let mut x = size / CHUNK_SIZE;
+ if size % CHUNK_SIZE != 0 {
+ x += 1;
+ }
+ x
+ };
+ let rounded_size = num_chunks * CHUNK_SIZE;
+
+ let mut descriptor_pool = unsafe {
+ use hal::pso::{DescriptorRangeDesc, DescriptorType, DescriptorPoolCreateFlags, ImageDescriptorType};
+
+ device.create_descriptor_pool(
+ num_chunks,
+ &[
+ DescriptorRangeDesc {
+ ty: DescriptorType::Image {
+ ty: ImageDescriptorType::Sampled {
+ with_sampler: false
+ }
+ },
+ count: rounded_size
+ },
+ DescriptorRangeDesc {
+ ty: DescriptorType::Sampler,
+ count: rounded_size
+ }
+ ],
+ DescriptorPoolCreateFlags::empty()
+ ).map_err(|e| {
+ println!("{:?}", e);
+ error::CreationError::OutOfMemoryError
+ })?
+ };
+
+ // Descriptor set layout
+ let mut descriptor_set_layout = unsafe {
+ use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags, ImageDescriptorType};
+
+ device.create_descriptor_set_layout(
+ &[
+ DescriptorSetLayoutBinding {
+ binding: 0,
+ ty: DescriptorType::Image {
+ ty: ImageDescriptorType::Sampled {
+ with_sampler: false
+ }
+ },
+ count: CHUNK_SIZE,
+ stage_flags: ShaderStageFlags::FRAGMENT,
+ immutable_samplers: false
+ },
+ DescriptorSetLayoutBinding {
+ binding: 1,
+ ty: DescriptorType::Sampler,
+ count: CHUNK_SIZE,
+ stage_flags: ShaderStageFlags::FRAGMENT,
+ immutable_samplers: false
+ }
+ ],
+ &[],
+ )
+ }.map_err(|_| error::CreationError::OutOfMemoryError)?;
+
+ // Set up all our chunks
+ debug!("Starting to load textures...");
+
+ let mut resolver = BasicFSResolver::new(Path::new("."));
+
+ let mut chunks = Vec::with_capacity(num_chunks);
+ for i in 0..num_chunks {
+ let range = {
+ let mut r = (i * CHUNK_SIZE) as u32..((i + 1) * CHUNK_SIZE) as u32;
+ if r.end > size as u32 {
+ r.end = size as u32;
+ }
+ r
+ };
+ debug!("Chunk {} / {} covering {:?}", i + 1, num_chunks, range);
+
+ chunks.push(TextureChunk::new(device, adapter, command_queue, command_pool, &mut descriptor_pool, &mut descriptor_set_layout, file, range, &mut resolver)?);
+ }
+
+ debug!("All textures loaded.");
+
+ Ok(TextureStore {
+ descriptor_pool: ManuallyDrop::new(descriptor_pool),
+ descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout),
+ chunks: chunks.into_boxed_slice()
+ })
+ }
+
+ pub fn deactivate(mut self, device: &mut Device) -> () {
+ unsafe {
+ use core::ptr::read;
+
+ for chunk in self.chunks.into_vec().drain(..) {
+ chunk.deactivate(device)
+ }
+
+ self.descriptor_pool.reset();
+ device
+ .destroy_descriptor_set_layout(ManuallyDrop::into_inner(read(&self.descriptor_set_layout)));
+ device.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&self.descriptor_pool)));
+ }
+ }
+
+ pub fn get_chunk_descriptor_set<'a>(&'a self, idx: usize) -> &'a DescriptorSet {
+ &self.chunks[idx].descriptor_set
+ }
+}
diff --git a/stockton-render/src/draw/texture/mod.rs b/stockton-render/src/draw/texture/mod.rs
new file mode 100644
index 0000000..896e1d2
--- /dev/null
+++ b/stockton-render/src/draw/texture/mod.rs
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// 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 <http://www.gnu.org/licenses/>.
+
+mod resolver;
+mod image;
+mod chunk;
+pub mod loader;
+
+pub use self::loader::TextureStore;
+pub use self::image::LoadedImage; \ No newline at end of file
diff --git a/stockton-render/src/draw/texture/resolver.rs b/stockton-render/src/draw/texture/resolver.rs
new file mode 100644
index 0000000..66bce9e
--- /dev/null
+++ b/stockton-render/src/draw/texture/resolver.rs
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// 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 <http://www.gnu.org/licenses/>.
+
+//! Resolves a texture in a BSP File to an image
+
+use stockton_levels::traits::textures::Texture;
+
+use image::{
+ RgbaImage,
+ io::Reader
+};
+
+use std::path::Path;
+
+/// An object that can be used to resolve a texture from a BSP File
+pub trait TextureResolver {
+ fn resolve(&mut self, texture: &Texture) -> RgbaImage;
+}
+
+pub struct BasicFSResolver<'a> {
+ path: &'a Path
+}
+
+impl<'a> BasicFSResolver<'a> {
+ pub fn new(path: &'a Path) -> BasicFSResolver<'a> {
+ BasicFSResolver {
+ path
+ }
+ }
+}
+
+impl<'a> TextureResolver for BasicFSResolver<'a> {
+ fn resolve(&mut self, tex: &Texture) -> RgbaImage {
+ let path = self.path.join(&tex.name);
+ println!("Loading texture from {:?}", path);
+
+ Reader::open(path).unwrap()
+ .with_guessed_format().unwrap()
+ .decode().unwrap()
+ .into_rgba()
+ }
+} \ No newline at end of file