From 24e25651c59441ef57a50e08de5a0806cff4f81b Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Sat, 25 Jul 2020 20:30:15 +0200 Subject: [PATCH] engine/render/vulkan: implement mipmaps --- engine/BUILD | 1 + engine/src/render/material.rs | 98 +----------- engine/src/render/vulkan/material.rs | 213 +++++++++++++++++++++++++++ engine/src/render/vulkan/mod.rs | 1 + 4 files changed, 218 insertions(+), 95 deletions(-) create mode 100644 engine/src/render/vulkan/material.rs diff --git a/engine/BUILD b/engine/BUILD index 1174447..c3332ab 100644 --- a/engine/BUILD +++ b/engine/BUILD @@ -19,6 +19,7 @@ rust_binary( "src/render/mod.rs", "src/render/renderable.rs", "src/render/vulkan/data.rs", + "src/render/vulkan/material.rs", "src/render/vulkan/mod.rs", "src/render/vulkan/pipeline.rs", "src/render/vulkan/pipeline_forward.rs", diff --git a/engine/src/render/material.rs b/engine/src/render/material.rs index cc5cc3a..2c0321d 100644 --- a/engine/src/render/material.rs +++ b/engine/src/render/material.rs @@ -19,113 +19,21 @@ use std::sync::Mutex; use std::time; use image; -use image::GenericImageView; use vulkano::device as vd; use vulkano::format as vf; use vulkano::image as vm; -use vulkano::sync::GpuFuture; use crate::physics::color; use crate::render::vulkan::data; +use crate::render::vulkan::material::ChannelLayoutVulkan; use crate::util::file; -pub trait ChannelLayout { - fn vulkan_from_image( - image: Arc, - graphics_queue: Arc, - ) -> Arc>; - - fn vulkan_from_value( - &self, - graphics_queue: Arc, - ) -> Arc>; -} - -impl ChannelLayout 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(); - // TODO(q3k): RGB -> CIE XYZ - let (image_view, future) = vm::ImmutableImage::from_iter( - rgba.into_raw().iter().cloned(), - vm::Dimensions::Dim2d{ width, height }, - vf::Format::R8G8B8A8Unorm, - graphics_queue.clone(), - ).unwrap(); - - future.flush().unwrap(); - image_view - } - - fn vulkan_from_value( - &self, - graphics_queue: Arc, - ) -> Arc> { - let mut image = image::ImageBuffer::, Vec>::new(1, 1); - image.put_pixel(0, 0, image::Rgba([self.x, self.y, self.z, 0.0])); - - let (image_view, future) = vm::ImmutableImage::from_iter( - image.into_raw().iter().cloned(), - vm::Dimensions::Dim2d{ width: 1, height: 1 }, - vf::Format::R32G32B32A32Sfloat, - graphics_queue.clone(), - ).unwrap(); - future.flush().unwrap(); - image_view - } -} - -impl ChannelLayout 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(); - let (image_view, future) = vm::ImmutableImage::from_iter( - gray.into_raw().iter().cloned(), - vm::Dimensions::Dim2d{ width, height }, - vf::Format::R8G8B8A8Unorm, - graphics_queue.clone(), - ).unwrap(); - - future.flush().unwrap(); - image_view - } - - 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 }, - vf::Format::R32Sfloat, - graphics_queue.clone(), - ).unwrap(); - - future.flush().unwrap(); - image_view - } -} - -pub enum Texture { +pub enum Texture { Color(T), ImageRef(String), } -impl Texture { +impl Texture { fn vulkan_image(&self, graphics_queue: Arc) -> Arc> { match self { Texture::::Color(c) => c.vulkan_from_value(graphics_queue), diff --git a/engine/src/render/vulkan/material.rs b/engine/src/render/vulkan/material.rs new file mode 100644 index 0000000..1da6fb7 --- /dev/null +++ b/engine/src/render/vulkan/material.rs @@ -0,0 +1,213 @@ +// 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 = 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 = 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 = 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(); + + // TODO(q3k): RGB -> CIE XYZ + mipmapped_from_iter(width, height, vf::Format::R8G8B8A8Unorm, rgba.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::Rgba([self.x, self.y, self.z, 0.0])); + + let (image_view, future) = vm::ImmutableImage::from_iter( + image.into_raw().iter().cloned(), + vm::Dimensions::Dim2d{ width: 1, height: 1 }, + 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::R8G8B8A8Unorm, 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 }, + 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()) + } +} diff --git a/engine/src/render/vulkan/mod.rs b/engine/src/render/vulkan/mod.rs index 0f0b1a5..7de52ed 100644 --- a/engine/src/render/vulkan/mod.rs +++ b/engine/src/render/vulkan/mod.rs @@ -27,6 +27,7 @@ use vulkano::swapchain as vs; use vulkano::sync::{FenceSignalFuture, GpuFuture}; pub mod data; +pub mod material; mod surface_binding; mod pipeline; mod pipeline_forward;