sim2d/streaming_renderer/texture/
loader.rs1use {
2 super::Texture,
3 crate::streaming_renderer::utility::round_to_power_of_two,
4 anyhow::{Context, Result},
5 ash::vk::{self},
6 demo_vk::graphics::vulkan::{CPUBuffer, SyncCommands, VulkanContext},
7 image::{DynamicImage, RgbaImage, imageops::FilterType},
8 std::{path::PathBuf, sync::Arc},
9};
10
11pub struct TextureLoader {
13 sync_commands: SyncCommands,
14 transfer_buffer: CPUBuffer<u8>,
15 ctx: Arc<VulkanContext>,
16}
17
18impl TextureLoader {
19 pub fn new(ctx: Arc<VulkanContext>) -> Result<Self> {
21 Ok(Self {
22 sync_commands: SyncCommands::new(ctx.clone()).context(
23 "Unable to create SyncCommands for the TextureLoader!",
24 )?,
25 transfer_buffer: CPUBuffer::allocate(
26 &ctx,
27 1024 * 1024,
28 vk::BufferUsageFlags::TRANSFER_SRC,
29 )
30 .context("Unable to allocate transfer buffer")?,
31 ctx,
32 })
33 }
34
35 pub fn load_from_file(
38 &mut self,
39 path: impl Into<PathBuf>,
40 generate_mipmaps: bool,
41 ) -> Result<Texture> {
42 let path: PathBuf = path.into();
43
44 let image = image::ImageReader::open(&path)
45 .context(format!("Unable to open image at {:?}", &path))?
46 .decode()
47 .context(format!("Unable to decode image from file {:?}", &path))?;
48
49 let mipmaps = if generate_mipmaps {
50 self.compute_generated_mipmaps(image)
51 } else {
52 vec![image.to_rgba8()]
53 };
54
55 let texture = Texture::builder()
56 .ctx(&self.ctx)
57 .dimensions(mipmaps[0].dimensions())
58 .format(vk::Format::R8G8B8A8_UNORM)
59 .image_usage_flags(
60 vk::ImageUsageFlags::TRANSFER_DST
61 | vk::ImageUsageFlags::SAMPLED,
62 )
63 .memory_property_flags(vk::MemoryPropertyFlags::DEVICE_LOCAL)
64 .mip_levels(mipmaps.len() as u32)
65 .build()
66 .context(format!(
67 "Unable to create texture for image file {:?}",
68 &path
69 ))?;
70
71 self.copy_data_to_transfer_buffer(&mipmaps)
72 .context("Unable to upload texture data!")?;
73
74 self.copy_transfer_buffer_to_device_memory(&texture, &mipmaps)
75 .context("Unable to copy transfer buffer to texture memory!")?;
76
77 Ok(texture)
78 }
79}
80
81impl TextureLoader {
83 fn compute_generated_mipmaps(
85 &self,
86 mut image: DynamicImage,
87 ) -> Vec<RgbaImage> {
88 let mut mipmaps = vec![image.to_rgba8()];
89 let mut width = image.width();
90 let mut height = image.height();
91 while width > 2 && height > 2 {
92 width /= 2;
93 height /= 2;
94 image = image.resize(width, height, FilterType::Lanczos3);
95 mipmaps.push(image.to_rgba8());
96 }
97 mipmaps
98 }
99
100 fn copy_data_to_transfer_buffer(
105 &mut self,
106 mipmaps: &[RgbaImage],
107 ) -> Result<()> {
108 let total_size = mipmaps.iter().map(|image| image.as_raw().len()).sum();
109 if (self.transfer_buffer.size_in_bytes() as usize) < total_size {
110 log::trace!(
111 "Existing transfer buffer {:?} needs reallocated",
112 self.transfer_buffer
113 );
114 self.transfer_buffer = CPUBuffer::allocate(
115 &self.ctx,
116 round_to_power_of_two(total_size),
117 vk::BufferUsageFlags::TRANSFER_SRC,
118 )
119 .context("Unable to reallocate transfer buffer")?;
120 log::trace!(
121 "New transfer buffer allocated: {:?}",
122 self.transfer_buffer
123 );
124 }
125 let mut offset = 0;
126 for mipmap in mipmaps {
127 unsafe {
134 self.transfer_buffer
135 .write_data(offset, mipmap.as_raw())
136 .context("Unable to write data to transfer buffer")?;
137 }
138 offset += mipmap.as_raw().len();
139 }
140 Ok(())
141 }
142
143 fn copy_transfer_buffer_to_device_memory(
146 &self,
147 texture: &Texture,
148 mipmaps: &[RgbaImage],
149 ) -> Result<()> {
150 let ctx = &self.ctx;
151 self.sync_commands.submit_and_wait(|command_buffer| unsafe {
152 ctx.cmd_pipeline_barrier(
153 command_buffer,
154 vk::PipelineStageFlags::TOP_OF_PIPE,
155 vk::PipelineStageFlags::TRANSFER,
156 vk::DependencyFlags::empty(),
157 &[],
158 &[],
159 &[vk::ImageMemoryBarrier {
160 src_access_mask: vk::AccessFlags::empty(),
161 dst_access_mask: vk::AccessFlags::TRANSFER_WRITE,
162 old_layout: vk::ImageLayout::UNDEFINED,
163 new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
164 src_queue_family_index: ctx.graphics_queue_family_index,
165 dst_queue_family_index: ctx.graphics_queue_family_index,
166 image: texture.image().raw,
167 subresource_range: vk::ImageSubresourceRange {
168 aspect_mask: vk::ImageAspectFlags::COLOR,
169 base_mip_level: 0,
170 level_count: mipmaps.len() as u32,
171 base_array_layer: 0,
172 layer_count: 1,
173 },
174 ..Default::default()
175 }],
176 );
177 let buffer_image_copies: Vec<vk::BufferImageCopy> = {
178 let mut offset: u64 = 0;
179 mipmaps
180 .iter()
181 .enumerate()
182 .map(|(mip_level, mipmap)| {
183 let buffer_image_copy = vk::BufferImageCopy {
184 buffer_offset: offset,
185 buffer_row_length: 0,
186 buffer_image_height: 0,
187 image_subresource: vk::ImageSubresourceLayers {
188 aspect_mask: vk::ImageAspectFlags::COLOR,
189 mip_level: mip_level as u32,
190 base_array_layer: 0,
191 layer_count: 1,
192 },
193 image_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
194 image_extent: vk::Extent3D {
195 width: mipmap.width(),
196 height: mipmap.height(),
197 depth: 1,
198 },
199 };
200 offset += mipmap.as_raw().len() as u64;
201 buffer_image_copy
202 })
203 .collect()
204 };
205 ctx.cmd_copy_buffer_to_image(
206 command_buffer,
207 self.transfer_buffer.buffer(),
208 texture.image().raw,
209 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
210 &buffer_image_copies,
211 );
212 ctx.cmd_pipeline_barrier(
213 command_buffer,
214 vk::PipelineStageFlags::TRANSFER,
215 vk::PipelineStageFlags::BOTTOM_OF_PIPE,
216 vk::DependencyFlags::empty(),
217 &[],
218 &[],
219 &[vk::ImageMemoryBarrier {
220 src_access_mask: vk::AccessFlags::TRANSFER_WRITE,
221 dst_access_mask: vk::AccessFlags::empty(),
222 old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
223 new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
224 src_queue_family_index: ctx.graphics_queue_family_index,
225 dst_queue_family_index: ctx.graphics_queue_family_index,
226 image: texture.image().raw,
227 subresource_range: vk::ImageSubresourceRange {
228 aspect_mask: vk::ImageAspectFlags::COLOR,
229 base_mip_level: 0,
230 level_count: mipmaps.len() as u32,
231 base_array_layer: 0,
232 layer_count: 1,
233 },
234 ..Default::default()
235 }],
236 );
237 Ok(())
238 }) }
240}
241
242#[cfg(test)]
243mod test {
244 use super::*;
245
246 #[test]
247 pub fn power_of_two_should_round_up() {
248 assert_eq!(round_to_power_of_two(1), 1);
249 assert_eq!(round_to_power_of_two(2), 2);
250 assert_eq!(round_to_power_of_two(3), 4);
251 assert_eq!(round_to_power_of_two(6), 8);
252 assert_eq!(round_to_power_of_two(9), 16);
253 assert_eq!(round_to_power_of_two(20), 32);
254 assert_eq!(round_to_power_of_two(50), 64);
255 assert_eq!(round_to_power_of_two(93), 128);
256 assert_eq!(round_to_power_of_two(200), 256);
257 assert_eq!(round_to_power_of_two(500), 512);
258 assert_eq!(round_to_power_of_two(10_000), 16384);
259 }
260}