Skip to main content

vk_graph_fx/
image_loader.rs

1use {
2    super::BitmapFont,
3    anyhow::{Context, bail},
4    bmfont::BMFont,
5    log::{info, warn},
6    std::sync::Arc,
7    vk_graph::{
8        Graph,
9        driver::{
10            DriverError,
11            ash::vk,
12            buffer::{Buffer, BufferInfo},
13            compute::{ComputePipeline, ComputePipelineInfo},
14            device::Device,
15            image::{Image, ImageInfo},
16            shader::Shader,
17            sync::AccessType,
18        },
19        pool::{Pool as _, hash::HashPool},
20    },
21    vk_shader_macros::include_glsl,
22};
23
24/// Describes the channels and pixel stride of an image format
25#[derive(Clone, Copy, Debug)]
26pub enum ImageFormat {
27    /// Single-channel 8-bit image data.
28    R8,
29
30    /// Two-channel 8-bit image data.
31    R8G8,
32
33    /// Three-channel 8-bit image data.
34    R8G8B8,
35
36    /// Four-channel 8-bit image data.
37    R8G8B8A8,
38}
39
40impl ImageFormat {
41    fn stride(self) -> usize {
42        match self {
43            Self::R8 => 1,
44            Self::R8G8 => 2,
45            Self::R8G8B8 => 3,
46            Self::R8G8B8A8 => 4,
47        }
48    }
49}
50
51/// Helper for decoding CPU bitmap data into `vk-graph` images.
52#[derive(Debug)]
53pub struct ImageLoader {
54    pool: HashPool,
55    _decode_r_rg: ComputePipeline,
56    decode_rgb_rgba: ComputePipeline,
57
58    /// The device used to create temporary buffers, images, and decode pipelines.
59    pub device: Device,
60}
61
62impl ImageLoader {
63    /// Creates a new image loader and its internal decode pipelines.
64    pub fn new(device: &Device) -> Result<Self, DriverError> {
65        Ok(Self {
66            pool: HashPool::new(device),
67            _decode_r_rg: ComputePipeline::create(
68                device,
69                ComputePipelineInfo::default(),
70                Shader::new_compute(
71                    include_glsl!("res/shader/compute/decode_bitmap_r_rg.comp").as_slice(),
72                ),
73            )?,
74            decode_rgb_rgba: ComputePipeline::create(
75                device,
76                ComputePipelineInfo::default(),
77                Shader::new_compute(
78                    include_glsl!("res/shader/compute/decode_bitmap_rgb_rgba.comp").as_slice(),
79                ),
80            )?,
81            device: device.clone(),
82        })
83    }
84
85    fn create_image(
86        &self,
87        format: ImageFormat,
88        width: u32,
89        height: u32,
90        is_srgb: bool,
91        is_temporary: bool,
92    ) -> anyhow::Result<Arc<Image>> {
93        let format = match format {
94            ImageFormat::R8 | ImageFormat::R8G8 => {
95                if is_temporary {
96                    vk::Format::R8G8_UINT
97                } else if is_srgb {
98                    panic!("Unsupported format: R8G8_SRGB");
99                } else {
100                    vk::Format::R8G8_UNORM
101                }
102            }
103            ImageFormat::R8G8B8 | ImageFormat::R8G8B8A8 => {
104                if is_temporary {
105                    vk::Format::R8G8B8A8_UINT
106                } else if is_srgb {
107                    vk::Format::R8G8B8A8_SRGB
108                } else {
109                    vk::Format::R8G8B8A8_UNORM
110                }
111            }
112        };
113        let usage = if is_temporary {
114            vk::ImageUsageFlags::STORAGE
115                | vk::ImageUsageFlags::TRANSFER_DST
116                | vk::ImageUsageFlags::TRANSFER_SRC
117        } else {
118            vk::ImageUsageFlags::SAMPLED
119                | vk::ImageUsageFlags::TRANSFER_DST
120                | vk::ImageUsageFlags::TRANSFER_SRC
121        };
122
123        Ok(Arc::new(
124            Image::create(
125                &self.device,
126                ImageInfo::image_2d(width, height, format, usage),
127            )
128            .context("Unable to create new image")?,
129        ))
130    }
131
132    /// Decodes bitmap pixels into an image and uploads it through a temporary graph submission.
133    #[allow(clippy::too_many_arguments)]
134    pub fn decode_bitmap(
135        &mut self,
136        queue_family_index: u32,
137        queue_index: u32,
138        pixels: &[u8],
139        format: ImageFormat,
140        width: u32,
141        height: u32,
142        is_srgb: bool,
143    ) -> anyhow::Result<Arc<Image>> {
144        info!(
145            "decoding {}x{} {:?} bitmap ({} K)",
146            width,
147            height,
148            format,
149            pixels.len() / 1024
150        );
151
152        debug_assert!(
153            pixels.len() >= format.stride() * (width * height) as usize,
154            "insufficient data"
155        );
156
157        if pixels.len() > (format.stride() as u32 * width * height).next_multiple_of(4) as usize {
158            warn!("unused data");
159        }
160
161        let mut graph = Graph::default();
162        let image = graph.bind_resource(self.create_image(format, width, height, is_srgb, false)?);
163
164        // Fill the image from the temporary buffer
165        match format {
166            ImageFormat::R8 => {
167                // This format requires a conversion
168                info!("Converting R to RG");
169                bail!("unsupported bitmap decode format: R8")
170            }
171            ImageFormat::R8G8B8 => {
172                // This format requires a conversion
173                //info!("Converting RGB to RGBA");
174
175                let stride = width * format.stride() as u32;
176
177                //trace!("{bitmap_width}x{bitmap_height} Stride={bitmap_stride}");
178
179                let pixel_buf_stride = stride.next_multiple_of(12);
180                let pixel_buf_len = (pixel_buf_stride * height) as vk::DeviceSize;
181
182                //trace!("pixel_buf_len={pixel_buf_len} pixel_buf_stride={pixel_buf_stride}");
183
184                // Lease a temporary buffer from the cache pool
185                let mut pixel_buf = self.pool.resource(BufferInfo::host_mem(
186                    pixel_buf_len,
187                    vk::BufferUsageFlags::STORAGE_BUFFER,
188                ))?;
189
190                {
191                    let pixel_buf =
192                        &mut Buffer::mapped_slice_mut(&mut pixel_buf)[0..pixel_buf_len as usize];
193
194                    // Fill the temporary buffer with the bitmap pixels - it has a different stride
195                    for y in 0..height {
196                        let src_offset = y * stride;
197                        let src = &pixels[src_offset as usize..(src_offset + stride) as usize];
198
199                        let dst_offset = y * pixel_buf_stride;
200                        let dst =
201                            &mut pixel_buf[dst_offset as usize..(dst_offset + stride) as usize];
202
203                        dst.copy_from_slice(src);
204                    }
205                }
206
207                let pixel_buf = graph.bind_resource(pixel_buf);
208
209                // We create a temporary storage image because SRGB support isn't wide enough to
210                // have SRGB storage images directly
211                let temp_image =
212                    graph.bind_resource(self.create_image(format, width, height, false, true)?);
213
214                // Copy host-local data in the buffer to the temporary buffer on the GPU and then
215                // use a compute shader to decode it before copying it over the output image
216
217                let dispatch_x = (width + 3) >> 2;
218                let dispatch_y = height;
219                graph
220                    .begin_cmd()
221                    .debug_name("Decode RGB image")
222                    .bind_pipeline(&self.decode_rgb_rgba)
223                    .shader_resource_access(0, pixel_buf, AccessType::ComputeShaderReadOther)
224                    .shader_resource_access(1, temp_image, AccessType::ComputeShaderWrite)
225                    .record_cmd(move |cmd| {
226                        cmd.push_constants(0, &(pixel_buf_stride >> 2).to_ne_bytes())
227                            .dispatch(dispatch_x, dispatch_y, 1);
228                    })
229                    .end_cmd()
230                    .copy_image(temp_image, image);
231            }
232            ImageFormat::R8G8 | ImageFormat::R8G8B8A8 => {
233                // Lease a temporary buffer from the pool
234                let mut pixel_buf = self.pool.resource(BufferInfo::host_mem(
235                    pixels.len() as _,
236                    vk::BufferUsageFlags::TRANSFER_SRC,
237                ))?;
238
239                {
240                    // Fill the temporary buffer with the bitmap pixels
241                    let pixel_buf = &mut Buffer::mapped_slice_mut(&mut pixel_buf)[0..pixels.len()];
242                    pixel_buf.copy_from_slice(pixels);
243                }
244
245                let pixel_buf = graph.bind_resource(pixel_buf);
246                graph.copy_buffer_to_image(pixel_buf, image);
247            }
248        }
249
250        let image = graph.resource(image).clone();
251
252        graph
253            .finalize()
254            .queue_submit(&mut self.pool, queue_family_index, queue_index)?;
255
256        Ok(image)
257    }
258
259    /// Decodes a linear-color bitmap into an image.
260    pub fn decode_linear(
261        &mut self,
262        queue_family_index: u32,
263        queue_index: u32,
264        pixels: &[u8],
265        format: ImageFormat,
266        width: u32,
267        height: u32,
268    ) -> anyhow::Result<Arc<Image>> {
269        self.decode_bitmap(
270            queue_family_index,
271            queue_index,
272            pixels,
273            format,
274            width,
275            height,
276            false,
277        )
278    }
279
280    /// Decodes an sRGB bitmap into an image.
281    pub fn decode_srgb(
282        &mut self,
283        queue_family_index: u32,
284        queue_index: u32,
285        pixels: &[u8],
286        format: ImageFormat,
287        width: u32,
288        height: u32,
289    ) -> anyhow::Result<Arc<Image>> {
290        self.decode_bitmap(
291            queue_family_index,
292            queue_index,
293            pixels,
294            format,
295            width,
296            height,
297            true,
298        )
299    }
300
301    /// Loads a bitmap font by decoding each supplied page image and pairing it with the font data.
302    pub fn load_bitmap_font<'a>(
303        &mut self,
304        queue_family_index: u32,
305        queue_index: u32,
306        font: BMFont,
307        pages: impl IntoIterator<Item = (&'a [u8], u32, u32)>,
308    ) -> anyhow::Result<BitmapFont> {
309        let pages = pages
310            .into_iter()
311            .map(|(pixels, width, height)| {
312                self.decode_linear(
313                    queue_family_index,
314                    queue_index,
315                    pixels,
316                    ImageFormat::R8G8B8,
317                    width,
318                    height,
319                )
320            })
321            .collect::<Result<Vec<_>, _>>()?;
322
323        BitmapFont::new(&self.device, font, pages)
324    }
325}
326
327#[cfg(test)]
328mod test {
329    use super::ImageFormat;
330
331    #[test]
332    fn image_format_stride_matches_channel_count() {
333        assert_eq!(ImageFormat::R8.stride(), 1);
334        assert_eq!(ImageFormat::R8G8.stride(), 2);
335        assert_eq!(ImageFormat::R8G8B8.stride(), 3);
336        assert_eq!(ImageFormat::R8G8B8A8.stride(), 4);
337    }
338}