sim2d/streaming_renderer/texture/
loader.rs

1use {
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
11/// A utility for loading textures from image files.
12pub struct TextureLoader {
13    sync_commands: SyncCommands,
14    transfer_buffer: CPUBuffer<u8>,
15    ctx: Arc<VulkanContext>,
16}
17
18impl TextureLoader {
19    /// Creates a new texture loader instance.
20    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    /// Synchronously loads a texture from an image file. Supports .png, .bmp,
36    /// and .jpg files
37    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
81// Private Functions
82impl TextureLoader {
83    /// Generates mipmaps for the image.
84    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    /// Copies data to the transfer buffer.
101    ///
102    /// This function may reallocate a new transfer buffer if the existing one
103    /// is too small for the data.
104    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            // # Safety
128            //
129            // Safe because the transfer buffer is not shared and is only used
130            // for synchronous commands sent to the GPU and because
131            // the transfer buffer is always resized to have enough
132            // capacity for all data.
133            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    /// Copies the contents of the transfer buffer into the texture's device
144    /// memory.
145    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        }) // sync_commands end
239    }
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}