// Copyright 2020 Sergiusz 'q3k' Bazanski // // This file is part of Abrasion. // // Abrasion 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, version 3. // // Abrasion 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 // Abrasion. If not, see . use std::sync::Arc; use image; use image::GenericImageView; use vulkano::command_buffer as vcb; use vulkano::buffer as vb; use vulkano::device as vd; use vulkano::format as vf; use vulkano::image as vm; use vulkano::sampler as vs; use vulkano::sync::GpuFuture; use vulkano::command_buffer::CommandBuffer; use crate::physics::color; /// Construct a mipmapped vulkan image from an iterator of raw data. fn mipmapped_from_iter( width: u32, height: u32, format: F, data: I, graphics_queue: Arc, ) -> Arc> where P: Send + Sync + Clone + 'static, I: ExactSizeIterator, F: vf::FormatDesc + vf::AcceptsPixels

+ 'static + Send + Sync + Copy, vf::Format: vf::AcceptsPixels

, { let dimensions = vm::Dimensions::Dim2d{ width, height }; let image_usage = vm::ImageUsage { transfer_destination: true, transfer_source: true, sampled: true, ..vm::ImageUsage::none() }; // Make a temporary image to generate mipmaps from. This is used to bypass // a bug in Vulkano's AutoCommandBufferBuilder that prevents blitting // inside the same image across different mipmaps. This is okay, as we plan // to move mipmap generation to compile-time anyway. let source = vb::CpuAccessibleBuffer::from_iter( graphics_queue.device().clone(), vb::BufferUsage::transfer_source(), false, data, ).unwrap(); let (mipmap_source_image, mipmap_source_image_init) = vm::ImmutableImage::uninitialized( graphics_queue.clone().device().clone(), dimensions, format, vm::MipmapsCount::One, vm::ImageUsage { transfer_destination: true, transfer_source: true, sampled: true, ..vm::ImageUsage::none() }, vm::ImageLayout::ShaderReadOnlyOptimal, graphics_queue.device().active_queue_families(), ).unwrap(); let (image, image_init) = vm::ImmutableImage::uninitialized( graphics_queue.clone().device().clone(), dimensions, format, vm::MipmapsCount::Log2, image_usage, vm::ImageLayout::ShaderReadOnlyOptimal, graphics_queue.device().active_queue_families(), ).unwrap(); let mut cb = vcb::AutoCommandBufferBuilder::new(graphics_queue.device().clone(), graphics_queue.family()).unwrap(); // Transfer buffer into mipmap_source_image. cb.copy_buffer_to_image_dimensions( source, mipmap_source_image_init, [0, 0, 0], dimensions.width_height_depth(), 0, dimensions.array_layers_with_cube(), 0, ).unwrap(); // Copy mip level 0 (original image) using image_init. cb.blit_image( mipmap_source_image.clone(), [0, 0, 0], [width as i32, height as i32, 1], 0, 0, image_init, [0, 0, 0], [width as i32, height as i32, 1], 0, 0, 1, vs::Filter::Linear ).unwrap(); // Generates all other mip levels using image. let img_dimensions = vm::ImageAccess::dimensions(&image); for mip_idx in 1..image.mipmap_levels() { let dest_dim = img_dimensions.mipmap_dimensions(mip_idx).unwrap(); cb.blit_image( mipmap_source_image.clone(), [0, 0, 0], [width as i32, height as i32, 1], 0, 0, image.clone(), [0, 0, 0], [dest_dim.width() as i32, dest_dim.height() as i32, 1i32], 0, mip_idx, 1, vs::Filter::Linear ).unwrap(); } let future = cb.build().unwrap().execute(graphics_queue.clone()).unwrap(); future.flush().unwrap(); image } /// Represents a layout of color channels whose users (single colors and textures) can be converted /// to Vulkan images. pub trait ChannelLayoutVulkan { fn vulkan_from_image( image: Arc, graphics_queue: Arc, ) -> Arc>; fn vulkan_from_value( &self, graphics_queue: Arc, ) -> Arc>; } impl ChannelLayoutVulkan for color::XYZ { fn vulkan_from_image( image: Arc, graphics_queue: Arc, ) -> Arc> { let (width, height) = (image.width(), image.height()); let rgba = image.to_rgba(); let mut xyz = Vec::new(); for (_, _, color) in rgba.enumerate_pixels() { let image::Rgba([r, g, b, a]) = color; let r = (*r as f32) / 255.0; let g = (*g as f32) / 255.0; let b = (*b as f32) / 255.0; let a = (*a as f32) / 255.0; let (x, y, z) = color::srgb_to_cie_xyz(r, g, b); xyz.push(x); xyz.push(y); xyz.push(z); xyz.push(a); } // TODO(q3k): RGB -> CIE XYZ mipmapped_from_iter(width, height, vf::Format::R32G32B32A32Sfloat, xyz.into_iter(), graphics_queue) } fn vulkan_from_value( &self, graphics_queue: Arc, ) -> Arc> { let (image_view, future) = vm::ImmutableImage::from_iter( vec!([self.x, self.y, self.z, 1.0 as f32]).into_iter(), vm::Dimensions::Dim2d{ width: 1, height: 1 }, vm::MipmapsCount::One, vf::Format::R32G32B32A32Sfloat, graphics_queue.clone(), ).unwrap(); future.flush().unwrap(); image_view } } impl ChannelLayoutVulkan for color::LinearF32 { fn vulkan_from_image( image: Arc, graphics_queue: Arc, ) -> Arc> { let (width, height) = (image.width(), image.height()); assert!(match image.color() { image::ColorType::L8 => true, image::ColorType::L16 => true, _ => false, }, "linearf32 texture must be 8-bit grayscale"); let gray = image.to_luma(); mipmapped_from_iter(width, height, vf::Format::R8Unorm, gray.into_raw().iter().cloned(), graphics_queue) } fn vulkan_from_value( &self, graphics_queue: Arc, ) -> Arc> { let mut image = image::ImageBuffer::, Vec>::new(1, 1); image.put_pixel(0, 0, image::Luma([self.d])); let (image_view, future) = vm::ImmutableImage::from_iter( image.into_raw().iter().cloned(), vm::Dimensions::Dim2d{ width: 1, height: 1 }, vm::MipmapsCount::One, vf::Format::R32Sfloat, graphics_queue.clone(), ).unwrap(); future.flush().unwrap(); image_view } } fn get_mip_dim(mip_idx: u32, img_dimensions: vm::ImageDimensions) -> Result<[i32; 3], String> { if let Some(dim) = img_dimensions.mipmap_dimensions(mip_idx) { if let vm::ImageDimensions::Dim2d { width, height, .. } = dim { Ok([width as i32, height as i32, 1]) } else { Err("MipMapping: Did not get 2D image for blitting".to_string()) } } else { Err(format!("MipMapping: image has no mip map at level {}", mip_idx).to_string()) } }