test(vk): Add Vulkan render test
This commit is contained in:
parent
6de2de8d12
commit
79513a301e
|
@ -42,9 +42,12 @@ use std::sync::Arc;
|
||||||
|
|
||||||
/// A Vulkan device and metadata that is required by the shader runtime.
|
/// A Vulkan device and metadata that is required by the shader runtime.
|
||||||
pub struct VulkanObjects {
|
pub struct VulkanObjects {
|
||||||
pub(crate) device: Arc<ash::Device>,
|
/// The handle to the initialized `ash::Device`
|
||||||
pub(crate) alloc: Arc<Mutex<Allocator>>,
|
pub device: Arc<ash::Device>,
|
||||||
queue: vk::Queue,
|
/// The instance of the `gpu-allocator` to use.
|
||||||
|
pub alloc: Arc<Mutex<Allocator>>,
|
||||||
|
/// The graphics queue to do work on.
|
||||||
|
pub queue: vk::Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of handles needed to access the Vulkan instance.
|
/// A collection of handles needed to access the Vulkan instance.
|
||||||
|
|
|
@ -16,6 +16,10 @@ ash = "0.38.0+1.3.281"
|
||||||
pollster = "0.3.0"
|
pollster = "0.3.0"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
image-compare = "0.4.1"
|
image-compare = "0.4.1"
|
||||||
|
gpu-allocator = "0.27.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
vulkan-debug = []
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.windows]
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
|
|
||||||
/// Render tests
|
/// Render tests
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod d3d11;
|
pub mod d3d11;
|
||||||
|
pub mod vk;
|
||||||
pub mod wgpu;
|
pub mod wgpu;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -27,7 +28,10 @@ mod test {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
const IMAGE_PATH: &str = "../triangle.png";
|
const IMAGE_PATH: &str = "../triangle.png";
|
||||||
const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
|
// const FILTER_PATH: &str = "../test/shaders_slang/crt/crt-royale.slangp";
|
||||||
|
|
||||||
|
const FILTER_PATH: &str =
|
||||||
|
"../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_d3d11() -> anyhow::Result<()> {
|
pub fn test_d3d11() -> anyhow::Result<()> {
|
||||||
|
@ -49,6 +53,16 @@ mod test {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_vk() -> anyhow::Result<()> {
|
||||||
|
let vulkan = super::vk::Vulkan::new(IMAGE_PATH)?;
|
||||||
|
let image = vulkan.render(FILTER_PATH, 100)?;
|
||||||
|
//
|
||||||
|
let out = File::create("out.png")?;
|
||||||
|
image.write_with_encoder(PngEncoder::new(out))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn compare() -> anyhow::Result<()> {
|
pub fn compare() -> anyhow::Result<()> {
|
||||||
let d3d11 = super::d3d11::Direct3D11::new(IMAGE_PATH)?;
|
let d3d11 = super::d3d11::Direct3D11::new(IMAGE_PATH)?;
|
||||||
|
|
166
librashader-test/src/render/vk/base.rs
Normal file
166
librashader-test/src/render/vk/base.rs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
use crate::render::vk::physical_device::QueueFamilyIndices;
|
||||||
|
use ash::prelude::VkResult;
|
||||||
|
use ash::vk;
|
||||||
|
use ash::vk::CommandBufferResetFlags;
|
||||||
|
use gpu_allocator::vulkan::Allocator;
|
||||||
|
use librashader::runtime::vk::VulkanObjects;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VulkanBase {
|
||||||
|
entry: ash::Entry,
|
||||||
|
instance: ash::Instance,
|
||||||
|
device: Arc<ash::Device>,
|
||||||
|
graphics_queue: vk::Queue,
|
||||||
|
// pub debug: VulkanDebug,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
mem_props: vk::PhysicalDeviceMemoryProperties,
|
||||||
|
allocator: Arc<Mutex<Allocator>>,
|
||||||
|
_pool: vk::CommandPool,
|
||||||
|
cmd_buffer: vk::CommandBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&VulkanBase> for VulkanObjects {
|
||||||
|
fn from(value: &VulkanBase) -> Self {
|
||||||
|
VulkanObjects {
|
||||||
|
device: Arc::clone(&value.device),
|
||||||
|
alloc: Arc::clone(&value.allocator),
|
||||||
|
queue: value.graphics_queue.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KHRONOS_VALIDATION: &[u8] = b"VK_LAYER_KHRONOS_validation\0";
|
||||||
|
const LIBRASHADER_VULKAN: &[u8] = b"librashader Vulkan\0";
|
||||||
|
|
||||||
|
impl VulkanBase {
|
||||||
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
|
let entry = unsafe { ash::Entry::load() }?;
|
||||||
|
let layers = [KHRONOS_VALIDATION.as_ptr().cast()];
|
||||||
|
let app_info = vk::ApplicationInfo::default()
|
||||||
|
.application_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
|
||||||
|
.engine_name(unsafe { CStr::from_bytes_with_nul_unchecked(LIBRASHADER_VULKAN) })
|
||||||
|
.engine_version(0)
|
||||||
|
.application_version(0)
|
||||||
|
.api_version(vk::make_api_version(0, 1, 3, 0));
|
||||||
|
|
||||||
|
let create_info = vk::InstanceCreateInfo::default()
|
||||||
|
.application_info(&app_info)
|
||||||
|
.enabled_layer_names(&layers)
|
||||||
|
.enabled_extension_names(&[]);
|
||||||
|
|
||||||
|
let instance = unsafe { entry.create_instance(&create_info, None) }?;
|
||||||
|
|
||||||
|
let physical_device = super::physical_device::pick_physical_device(&instance)?;
|
||||||
|
|
||||||
|
let (device, queue, cmd_pool) = Self::create_device(&instance, &physical_device)?;
|
||||||
|
let mem_props = unsafe { instance.get_physical_device_memory_properties(physical_device) };
|
||||||
|
|
||||||
|
let alloc = super::memory::create_allocator(
|
||||||
|
device.clone(),
|
||||||
|
instance.clone(),
|
||||||
|
physical_device.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let buffer_info = vk::CommandBufferAllocateInfo::default()
|
||||||
|
.command_pool(cmd_pool)
|
||||||
|
.level(vk::CommandBufferLevel::PRIMARY)
|
||||||
|
.command_buffer_count(1);
|
||||||
|
|
||||||
|
let buffers = unsafe { device.allocate_command_buffers(&buffer_info) }?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
entry,
|
||||||
|
instance,
|
||||||
|
device: Arc::new(device),
|
||||||
|
graphics_queue: queue,
|
||||||
|
physical_device,
|
||||||
|
mem_props,
|
||||||
|
// debug,
|
||||||
|
allocator: alloc,
|
||||||
|
_pool: cmd_pool,
|
||||||
|
cmd_buffer: buffers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn device(&self) -> &Arc<ash::Device> {
|
||||||
|
&self.device
|
||||||
|
}
|
||||||
|
pub(crate) fn allocator(&self) -> &Arc<Mutex<Allocator>> {
|
||||||
|
&self.allocator
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_device(
|
||||||
|
instance: &ash::Instance,
|
||||||
|
physical_device: &vk::PhysicalDevice,
|
||||||
|
) -> anyhow::Result<(ash::Device, vk::Queue, vk::CommandPool)> {
|
||||||
|
let _debug = [unsafe { CStr::from_bytes_with_nul_unchecked(KHRONOS_VALIDATION).as_ptr() }];
|
||||||
|
let indices = super::physical_device::find_queue_family(&instance, *physical_device);
|
||||||
|
|
||||||
|
let queue_info = [vk::DeviceQueueCreateInfo::default()
|
||||||
|
.queue_family_index(indices.graphics_family()?)
|
||||||
|
.queue_priorities(&[1.0f32])];
|
||||||
|
|
||||||
|
let mut physical_device_features =
|
||||||
|
vk::PhysicalDeviceVulkan13Features::default().dynamic_rendering(true);
|
||||||
|
|
||||||
|
let extensions = [ash::khr::dynamic_rendering::NAME.as_ptr()];
|
||||||
|
|
||||||
|
let device_create_info = vk::DeviceCreateInfo::default()
|
||||||
|
.queue_create_infos(&queue_info)
|
||||||
|
.enabled_extension_names(&extensions)
|
||||||
|
.push_next(&mut physical_device_features);
|
||||||
|
|
||||||
|
let device =
|
||||||
|
unsafe { instance.create_device(*physical_device, &device_create_info, None)? };
|
||||||
|
|
||||||
|
let queue = unsafe { device.get_device_queue(indices.graphics_family()?, 0) };
|
||||||
|
|
||||||
|
let create_info = vk::CommandPoolCreateInfo::default()
|
||||||
|
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
|
||||||
|
.queue_family_index(indices.graphics_family()?);
|
||||||
|
let pool = unsafe { device.create_command_pool(&create_info, None) }?;
|
||||||
|
|
||||||
|
Ok((device, queue, pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple helper function to synchronously queue work on the graphics queue
|
||||||
|
pub fn queue_work<T>(&self, f: impl FnOnce(vk::CommandBuffer) -> T) -> anyhow::Result<T> {
|
||||||
|
unsafe {
|
||||||
|
self.device.begin_command_buffer(
|
||||||
|
self.cmd_buffer,
|
||||||
|
&vk::CommandBufferBeginInfo::default()
|
||||||
|
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = f(self.cmd_buffer);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.device.end_command_buffer(self.cmd_buffer)?;
|
||||||
|
|
||||||
|
self.device.queue_submit(
|
||||||
|
self.graphics_queue,
|
||||||
|
&[vk::SubmitInfo::default().command_buffers(&[self.cmd_buffer])],
|
||||||
|
vk::Fence::null(),
|
||||||
|
)?;
|
||||||
|
self.device.queue_wait_idle(self.graphics_queue)?;
|
||||||
|
self.device
|
||||||
|
.reset_command_buffer(self.cmd_buffer, vk::CommandBufferResetFlags::empty())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanBase {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.device.destroy_command_pool(self._pool, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
librashader-test/src/render/vk/memory.rs
Normal file
136
librashader-test/src/render/vk/memory.rs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
use ash::vk;
|
||||||
|
use gpu_allocator::vulkan::{
|
||||||
|
Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
|
||||||
|
};
|
||||||
|
use gpu_allocator::{AllocationSizes, MemoryLocation};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct VulkanImageMemory {
|
||||||
|
pub(crate) allocation: ManuallyDrop<Allocation>,
|
||||||
|
allocator: Arc<Mutex<Allocator>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanImageMemory {
|
||||||
|
pub fn new(
|
||||||
|
device: &Arc<ash::Device>,
|
||||||
|
allocator: &Arc<Mutex<Allocator>>,
|
||||||
|
requirements: vk::MemoryRequirements,
|
||||||
|
image: &vk::Image,
|
||||||
|
location: MemoryLocation,
|
||||||
|
) -> anyhow::Result<VulkanImageMemory> {
|
||||||
|
let allocation = allocator.lock().allocate(&AllocationCreateDesc {
|
||||||
|
name: "imagemem",
|
||||||
|
requirements,
|
||||||
|
location,
|
||||||
|
linear: false,
|
||||||
|
allocation_scheme: AllocationScheme::DedicatedImage(*image),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
device.bind_image_memory(*image, allocation.memory(), 0)?;
|
||||||
|
Ok(VulkanImageMemory {
|
||||||
|
allocation: ManuallyDrop::new(allocation),
|
||||||
|
allocator: Arc::clone(allocator),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanImageMemory {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let allocation = unsafe { ManuallyDrop::take(&mut self.allocation) };
|
||||||
|
if let Err(e) = self.allocator.lock().free(allocation) {
|
||||||
|
println!("librashader-runtime-vk: [warn] failed to deallocate image buffer {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VulkanBuffer {
|
||||||
|
pub handle: vk::Buffer,
|
||||||
|
device: Arc<ash::Device>,
|
||||||
|
allocation: ManuallyDrop<Allocation>,
|
||||||
|
allocator: Arc<Mutex<Allocator>>,
|
||||||
|
size: vk::DeviceSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanBuffer {
|
||||||
|
pub fn new(
|
||||||
|
device: &Arc<ash::Device>,
|
||||||
|
allocator: &Arc<Mutex<Allocator>>,
|
||||||
|
usage: vk::BufferUsageFlags,
|
||||||
|
size: usize,
|
||||||
|
) -> anyhow::Result<VulkanBuffer> {
|
||||||
|
unsafe {
|
||||||
|
let buffer_info = vk::BufferCreateInfo::default()
|
||||||
|
.size(size as vk::DeviceSize)
|
||||||
|
.usage(usage)
|
||||||
|
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
let buffer = device.create_buffer(&buffer_info, None)?;
|
||||||
|
|
||||||
|
let memory_reqs = device.get_buffer_memory_requirements(buffer);
|
||||||
|
|
||||||
|
let alloc = allocator.lock().allocate(&AllocationCreateDesc {
|
||||||
|
name: "buffer",
|
||||||
|
requirements: memory_reqs,
|
||||||
|
location: MemoryLocation::CpuToGpu,
|
||||||
|
linear: true,
|
||||||
|
allocation_scheme: AllocationScheme::DedicatedBuffer(buffer),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// let alloc = device.allocate_memory(&alloc_info, None)?;
|
||||||
|
device.bind_buffer_memory(buffer, alloc.memory(), 0)?;
|
||||||
|
|
||||||
|
Ok(VulkanBuffer {
|
||||||
|
handle: buffer,
|
||||||
|
allocation: ManuallyDrop::new(alloc),
|
||||||
|
allocator: Arc::clone(allocator),
|
||||||
|
size: size as vk::DeviceSize,
|
||||||
|
device: device.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_mut_slice(&mut self) -> anyhow::Result<&mut [u8]> {
|
||||||
|
let Some(allocation) = self.allocation.mapped_slice_mut() else {
|
||||||
|
return Err(anyhow!("Allocation is not host visible"));
|
||||||
|
};
|
||||||
|
Ok(allocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanBuffer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: things can not be double dropped.
|
||||||
|
let allocation = ManuallyDrop::take(&mut self.allocation);
|
||||||
|
if let Err(e) = self.allocator.lock().free(allocation) {
|
||||||
|
println!("librashader-test-vk: [warn] failed to deallocate buffer memory {e}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.handle != vk::Buffer::null() {
|
||||||
|
self.device.destroy_buffer(self.handle, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_allocator(
|
||||||
|
device: ash::Device,
|
||||||
|
instance: ash::Instance,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
) -> Arc<Mutex<Allocator>> {
|
||||||
|
let alloc = Allocator::new(&AllocatorCreateDesc {
|
||||||
|
instance,
|
||||||
|
device,
|
||||||
|
physical_device,
|
||||||
|
debug_settings: Default::default(),
|
||||||
|
buffer_device_address: false,
|
||||||
|
allocation_sizes: AllocationSizes::default(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
Arc::new(Mutex::new(alloc))
|
||||||
|
}
|
360
librashader-test/src/render/vk/mod.rs
Normal file
360
librashader-test/src/render/vk/mod.rs
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
use crate::render::vk::base::VulkanBase;
|
||||||
|
use crate::render::vk::memory::{VulkanBuffer, VulkanImageMemory};
|
||||||
|
use crate::render::RenderTest;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use ash::vk;
|
||||||
|
use gpu_allocator::MemoryLocation;
|
||||||
|
use image::RgbaImage;
|
||||||
|
use librashader::runtime::vk::{FilterChain, FilterChainOptions, VulkanImage};
|
||||||
|
use librashader::runtime::Viewport;
|
||||||
|
use librashader_runtime::image::{Image, UVDirection, BGRA8};
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
mod base;
|
||||||
|
mod memory;
|
||||||
|
mod physical_device;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub struct Vulkan {
|
||||||
|
vk: VulkanBase,
|
||||||
|
image_bytes: Image<BGRA8>,
|
||||||
|
image: vk::Image,
|
||||||
|
view: vk::ImageView,
|
||||||
|
image_alloc: VulkanImageMemory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderTest for Vulkan {
|
||||||
|
fn render(&self, path: impl AsRef<Path>, frame_count: usize) -> anyhow::Result<RgbaImage> {
|
||||||
|
unsafe {
|
||||||
|
let mut filter_chain = FilterChain::load_from_path(
|
||||||
|
path,
|
||||||
|
&self.vk,
|
||||||
|
Some(&FilterChainOptions {
|
||||||
|
frames_in_flight: 3,
|
||||||
|
force_no_mipmaps: false,
|
||||||
|
use_dynamic_rendering: false,
|
||||||
|
disable_cache: true,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let image_info = vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.format(vk::Format::B8G8R8A8_UNORM)
|
||||||
|
.extent(self.image_bytes.size.into())
|
||||||
|
.mip_levels(1)
|
||||||
|
.array_layers(1)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.tiling(vk::ImageTiling::OPTIMAL)
|
||||||
|
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||||
|
|
||||||
|
let render_texture = unsafe { self.vk.device().create_image(&image_info, None)? };
|
||||||
|
|
||||||
|
// This just needs to stay alive until the read.
|
||||||
|
let _memory = unsafe {
|
||||||
|
let mem_reqs = self
|
||||||
|
.vk
|
||||||
|
.device()
|
||||||
|
.get_image_memory_requirements(render_texture);
|
||||||
|
VulkanImageMemory::new(
|
||||||
|
self.vk.device(),
|
||||||
|
&self.vk.allocator(),
|
||||||
|
mem_reqs,
|
||||||
|
&render_texture,
|
||||||
|
MemoryLocation::GpuOnly,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer_texture = self.vk.device().create_image(
|
||||||
|
&vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.format(vk::Format::R8G8B8A8_UNORM)
|
||||||
|
.extent(self.image_bytes.size.into())
|
||||||
|
.mip_levels(1)
|
||||||
|
.array_layers(1)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.tiling(vk::ImageTiling::LINEAR)
|
||||||
|
.usage(vk::ImageUsageFlags::TRANSFER_DST)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut transfer_memory = unsafe {
|
||||||
|
let mem_reqs = self
|
||||||
|
.vk
|
||||||
|
.device()
|
||||||
|
.get_image_memory_requirements(transfer_texture);
|
||||||
|
VulkanImageMemory::new(
|
||||||
|
self.vk.device(),
|
||||||
|
&self.vk.allocator(),
|
||||||
|
mem_reqs,
|
||||||
|
&transfer_texture,
|
||||||
|
MemoryLocation::GpuToCpu,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
self.vk.queue_work(|cmd| unsafe {
|
||||||
|
unsafe {
|
||||||
|
util::vulkan_image_layout_transition_levels(
|
||||||
|
&self.vk.device(),
|
||||||
|
cmd,
|
||||||
|
render_texture,
|
||||||
|
vk::REMAINING_MIP_LEVELS,
|
||||||
|
vk::ImageLayout::UNDEFINED,
|
||||||
|
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||||
|
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||||
|
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||||
|
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
);
|
||||||
|
|
||||||
|
util::vulkan_image_layout_transition_levels(
|
||||||
|
&self.vk.device(),
|
||||||
|
cmd,
|
||||||
|
transfer_texture,
|
||||||
|
vk::REMAINING_MIP_LEVELS,
|
||||||
|
vk::ImageLayout::UNDEFINED,
|
||||||
|
vk::ImageLayout::GENERAL,
|
||||||
|
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
|
||||||
|
vk::AccessFlags::TRANSFER_WRITE | vk::AccessFlags::TRANSFER_READ,
|
||||||
|
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||||
|
vk::PipelineStageFlags::TRANSFER,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_chain.frame(
|
||||||
|
&VulkanImage {
|
||||||
|
image: self.image,
|
||||||
|
size: self.image_bytes.size,
|
||||||
|
format: vk::Format::B8G8R8A8_UNORM,
|
||||||
|
},
|
||||||
|
&Viewport::new_render_target_sized_origin(
|
||||||
|
VulkanImage {
|
||||||
|
image: render_texture,
|
||||||
|
size: self.image_bytes.size.into(),
|
||||||
|
format: vk::Format::B8G8R8A8_UNORM,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)?,
|
||||||
|
cmd,
|
||||||
|
frame_count,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
util::vulkan_image_layout_transition_levels(
|
||||||
|
&self.vk.device(),
|
||||||
|
cmd,
|
||||||
|
render_texture,
|
||||||
|
vk::REMAINING_MIP_LEVELS,
|
||||||
|
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
|
vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
|
||||||
|
vk::AccessFlags::TRANSFER_READ,
|
||||||
|
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||||
|
vk::PipelineStageFlags::TRANSFER,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let offsets = [
|
||||||
|
vk::Offset3D { x: 0, y: 0, z: 0 },
|
||||||
|
vk::Offset3D {
|
||||||
|
x: self.image_bytes.size.width as i32,
|
||||||
|
y: self.image_bytes.size.height as i32,
|
||||||
|
z: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let subresource = vk::ImageSubresourceLayers::default()
|
||||||
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||||
|
.base_array_layer(0)
|
||||||
|
.layer_count(1);
|
||||||
|
|
||||||
|
let image_blit = vk::ImageBlit::default()
|
||||||
|
.src_subresource(subresource.clone())
|
||||||
|
.src_offsets(offsets.clone())
|
||||||
|
.dst_subresource(subresource)
|
||||||
|
.dst_offsets(offsets);
|
||||||
|
|
||||||
|
self.vk.device().cmd_blit_image(
|
||||||
|
cmd,
|
||||||
|
render_texture,
|
||||||
|
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
|
transfer_texture,
|
||||||
|
vk::ImageLayout::GENERAL,
|
||||||
|
&[image_blit],
|
||||||
|
vk::Filter::NEAREST,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
})??;
|
||||||
|
|
||||||
|
// should have read now.
|
||||||
|
let mut memory = transfer_memory
|
||||||
|
.allocation
|
||||||
|
.mapped_slice_mut()
|
||||||
|
.ok_or(anyhow!("readback buffer was not mapped"))?;
|
||||||
|
|
||||||
|
let layout = self.vk.device().get_image_subresource_layout(
|
||||||
|
transfer_texture,
|
||||||
|
vk::ImageSubresource::default()
|
||||||
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||||
|
.array_layer(0),
|
||||||
|
);
|
||||||
|
memory = &mut memory[layout.offset as usize..];
|
||||||
|
|
||||||
|
// let mut pixels = Vec::with_capacity(layout.size as usize);
|
||||||
|
|
||||||
|
let image = RgbaImage::from_raw(
|
||||||
|
self.image_bytes.size.width,
|
||||||
|
self.image_bytes.size.height,
|
||||||
|
Vec::from(memory),
|
||||||
|
)
|
||||||
|
.ok_or(anyhow!("failed to create image from slice"));
|
||||||
|
|
||||||
|
self.vk.device().destroy_image(transfer_texture, None);
|
||||||
|
self.vk.device().destroy_image(render_texture, None);
|
||||||
|
|
||||||
|
Ok(image?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vulkan {
|
||||||
|
pub fn new(image_path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||||
|
let vk = VulkanBase::new()?;
|
||||||
|
|
||||||
|
let (image_bytes, image_alloc, image, view) = Self::load_image(&vk, image_path)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
vk,
|
||||||
|
image,
|
||||||
|
image_bytes,
|
||||||
|
view,
|
||||||
|
image_alloc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_image(
|
||||||
|
vk: &VulkanBase,
|
||||||
|
image_path: impl AsRef<Path>,
|
||||||
|
) -> anyhow::Result<(Image<BGRA8>, VulkanImageMemory, vk::Image, vk::ImageView)> {
|
||||||
|
let image: Image<BGRA8> = Image::load(image_path, UVDirection::TopLeft)?;
|
||||||
|
|
||||||
|
let image_info = vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.format(vk::Format::B8G8R8A8_UNORM)
|
||||||
|
.extent(image.size.into())
|
||||||
|
.mip_levels(1)
|
||||||
|
.array_layers(1)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.tiling(vk::ImageTiling::OPTIMAL)
|
||||||
|
.usage(
|
||||||
|
vk::ImageUsageFlags::SAMPLED
|
||||||
|
| vk::ImageUsageFlags::TRANSFER_SRC
|
||||||
|
| vk::ImageUsageFlags::TRANSFER_DST,
|
||||||
|
)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||||
|
|
||||||
|
let texture = unsafe { vk.device().create_image(&image_info, None)? };
|
||||||
|
|
||||||
|
let memory = unsafe {
|
||||||
|
let mem_reqs = vk.device().get_image_memory_requirements(texture);
|
||||||
|
VulkanImageMemory::new(
|
||||||
|
vk.device(),
|
||||||
|
&vk.allocator(),
|
||||||
|
mem_reqs,
|
||||||
|
&texture,
|
||||||
|
MemoryLocation::GpuOnly,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let image_subresource = vk::ImageSubresourceRange::default()
|
||||||
|
.level_count(image_info.mip_levels)
|
||||||
|
.layer_count(1)
|
||||||
|
.aspect_mask(vk::ImageAspectFlags::COLOR);
|
||||||
|
|
||||||
|
let swizzle_components = vk::ComponentMapping::default()
|
||||||
|
.r(vk::ComponentSwizzle::R)
|
||||||
|
.g(vk::ComponentSwizzle::G)
|
||||||
|
.b(vk::ComponentSwizzle::B)
|
||||||
|
.a(vk::ComponentSwizzle::A);
|
||||||
|
|
||||||
|
let view_info = vk::ImageViewCreateInfo::default()
|
||||||
|
.view_type(vk::ImageViewType::TYPE_2D)
|
||||||
|
.format(vk::Format::B8G8R8A8_UNORM)
|
||||||
|
.image(texture)
|
||||||
|
.subresource_range(image_subresource)
|
||||||
|
.components(swizzle_components);
|
||||||
|
|
||||||
|
let texture_view = unsafe { vk.device().create_image_view(&view_info, None)? };
|
||||||
|
|
||||||
|
let mut staging = VulkanBuffer::new(
|
||||||
|
&vk.device(),
|
||||||
|
&vk.allocator(),
|
||||||
|
vk::BufferUsageFlags::TRANSFER_SRC,
|
||||||
|
image.bytes.len(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
staging.as_mut_slice()?.copy_from_slice(&image.bytes);
|
||||||
|
|
||||||
|
vk.queue_work(|cmd| unsafe {
|
||||||
|
util::vulkan_image_layout_transition_levels(
|
||||||
|
&vk.device(),
|
||||||
|
cmd,
|
||||||
|
texture,
|
||||||
|
vk::REMAINING_MIP_LEVELS,
|
||||||
|
vk::ImageLayout::UNDEFINED,
|
||||||
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
vk::AccessFlags::empty(),
|
||||||
|
vk::AccessFlags::TRANSFER_WRITE,
|
||||||
|
vk::PipelineStageFlags::TOP_OF_PIPE,
|
||||||
|
vk::PipelineStageFlags::TRANSFER,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
);
|
||||||
|
|
||||||
|
let builder = vk::BufferImageCopy::default()
|
||||||
|
.image_subresource(
|
||||||
|
vk::ImageSubresourceLayers::default()
|
||||||
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||||
|
.mip_level(0)
|
||||||
|
.base_array_layer(0)
|
||||||
|
.layer_count(1),
|
||||||
|
)
|
||||||
|
.image_extent(image.size.into());
|
||||||
|
|
||||||
|
vk.device().cmd_copy_buffer_to_image(
|
||||||
|
cmd,
|
||||||
|
staging.handle,
|
||||||
|
texture,
|
||||||
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
&[builder],
|
||||||
|
);
|
||||||
|
|
||||||
|
util::vulkan_image_layout_transition_levels(
|
||||||
|
&vk.device(),
|
||||||
|
cmd,
|
||||||
|
texture,
|
||||||
|
vk::REMAINING_MIP_LEVELS,
|
||||||
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
|
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
vk::AccessFlags::TRANSFER_WRITE,
|
||||||
|
vk::AccessFlags::SHADER_READ,
|
||||||
|
vk::PipelineStageFlags::TRANSFER,
|
||||||
|
vk::PipelineStageFlags::ALL_GRAPHICS,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
vk::QUEUE_FAMILY_IGNORED,
|
||||||
|
);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((image, memory, texture, texture_view))
|
||||||
|
}
|
||||||
|
}
|
144
librashader-test/src/render/vk/physical_device.rs
Normal file
144
librashader-test/src/render/vk/physical_device.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use ash::vk;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
|
pub struct QueueFamilyIndices {
|
||||||
|
graphics_family: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueueFamilyIndices {
|
||||||
|
pub fn is_complete(&self) -> bool {
|
||||||
|
self.graphics_family.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn graphics_family(&self) -> anyhow::Result<u32> {
|
||||||
|
self.graphics_family
|
||||||
|
.ok_or(anyhow!("The queue family is not complete"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pick_physical_device(instance: &ash::Instance) -> anyhow::Result<vk::PhysicalDevice> {
|
||||||
|
let physical_devices = unsafe { instance.enumerate_physical_devices()? };
|
||||||
|
|
||||||
|
let mut result = None;
|
||||||
|
for &physical_device in physical_devices.iter() {
|
||||||
|
if is_physical_device_suitable(instance, physical_device) && result.is_none() {
|
||||||
|
result = Some(physical_device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ok_or(anyhow!("Failed to find a suitable GPU"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_physical_device_suitable(
|
||||||
|
instance: &ash::Instance,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
) -> bool {
|
||||||
|
let device_properties = unsafe { instance.get_physical_device_properties(physical_device) };
|
||||||
|
let device_features = unsafe { instance.get_physical_device_features(physical_device) };
|
||||||
|
let device_queue_families =
|
||||||
|
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||||
|
|
||||||
|
if cfg!(feature = "vulkan-debug") {
|
||||||
|
let device_type = match device_properties.device_type {
|
||||||
|
vk::PhysicalDeviceType::CPU => "Cpu",
|
||||||
|
vk::PhysicalDeviceType::INTEGRATED_GPU => "Integrated GPU",
|
||||||
|
vk::PhysicalDeviceType::DISCRETE_GPU => "Discrete GPU",
|
||||||
|
vk::PhysicalDeviceType::VIRTUAL_GPU => "Virtual GPU",
|
||||||
|
vk::PhysicalDeviceType::OTHER => "Unknown",
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let device_name = unsafe { CStr::from_ptr(device_properties.device_name.as_ptr()) };
|
||||||
|
|
||||||
|
let device_name = device_name.to_string_lossy();
|
||||||
|
println!(
|
||||||
|
"\tDevice Name: {}, id: {}, type: {}",
|
||||||
|
device_name, device_properties.device_id, device_type
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("\tAPI Version: {}", device_properties.api_version);
|
||||||
|
|
||||||
|
println!("\tSupport Queue Family: {}", device_queue_families.len());
|
||||||
|
println!("\t\tQueue Count | Graphics, Compute, Transfer, Sparse Binding");
|
||||||
|
for queue_family in device_queue_families.iter() {
|
||||||
|
let is_graphics_support = if queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
|
||||||
|
{
|
||||||
|
"support"
|
||||||
|
} else {
|
||||||
|
"unsupport"
|
||||||
|
};
|
||||||
|
let is_compute_support = if queue_family.queue_flags.contains(vk::QueueFlags::COMPUTE) {
|
||||||
|
"support"
|
||||||
|
} else {
|
||||||
|
"unsupport"
|
||||||
|
};
|
||||||
|
let is_transfer_support = if queue_family.queue_flags.contains(vk::QueueFlags::TRANSFER)
|
||||||
|
{
|
||||||
|
"support"
|
||||||
|
} else {
|
||||||
|
"unsupport"
|
||||||
|
};
|
||||||
|
let is_sparse_support = if queue_family
|
||||||
|
.queue_flags
|
||||||
|
.contains(vk::QueueFlags::SPARSE_BINDING)
|
||||||
|
{
|
||||||
|
"support"
|
||||||
|
} else {
|
||||||
|
"unsupport"
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"\t\t{}\t | {}, {}, {}, {}",
|
||||||
|
queue_family.queue_count,
|
||||||
|
is_graphics_support,
|
||||||
|
is_compute_support,
|
||||||
|
is_transfer_support,
|
||||||
|
is_sparse_support
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are plenty of features
|
||||||
|
println!(
|
||||||
|
"\tGeometry Shader support: {}",
|
||||||
|
if device_features.geometry_shader == 1 {
|
||||||
|
"support"
|
||||||
|
} else {
|
||||||
|
"unsupported"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let indices = find_queue_family(instance, physical_device);
|
||||||
|
|
||||||
|
indices.is_complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_queue_family(
|
||||||
|
instance: &ash::Instance,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
) -> QueueFamilyIndices {
|
||||||
|
let queue_families =
|
||||||
|
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||||
|
|
||||||
|
let mut queue_family_indices = QueueFamilyIndices {
|
||||||
|
graphics_family: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut index = 0;
|
||||||
|
for queue_family in queue_families.iter() {
|
||||||
|
if queue_family.queue_count > 0
|
||||||
|
&& queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
|
||||||
|
{
|
||||||
|
queue_family_indices.graphics_family = Some(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if queue_family_indices.is_complete() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_family_indices
|
||||||
|
}
|
47
librashader-test/src/render/vk/util.rs
Normal file
47
librashader-test/src/render/vk/util.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use ash::vk;
|
||||||
|
use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc};
|
||||||
|
|
||||||
|
use gpu_allocator::AllocationSizes;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn vulkan_image_layout_transition_levels(
|
||||||
|
device: &ash::Device,
|
||||||
|
cmd: vk::CommandBuffer,
|
||||||
|
image: vk::Image,
|
||||||
|
levels: u32,
|
||||||
|
old_layout: vk::ImageLayout,
|
||||||
|
new_layout: vk::ImageLayout,
|
||||||
|
src_access: vk::AccessFlags,
|
||||||
|
dst_access: vk::AccessFlags,
|
||||||
|
src_stage: vk::PipelineStageFlags,
|
||||||
|
dst_stage: vk::PipelineStageFlags,
|
||||||
|
|
||||||
|
src_queue_family_index: u32,
|
||||||
|
dst_queue_family_index: u32,
|
||||||
|
) {
|
||||||
|
let mut barrier = vk::ImageMemoryBarrier::default();
|
||||||
|
barrier.s_type = vk::StructureType::IMAGE_MEMORY_BARRIER;
|
||||||
|
barrier.p_next = std::ptr::null();
|
||||||
|
barrier.src_access_mask = src_access;
|
||||||
|
barrier.dst_access_mask = dst_access;
|
||||||
|
barrier.old_layout = old_layout;
|
||||||
|
barrier.new_layout = new_layout;
|
||||||
|
barrier.src_queue_family_index = src_queue_family_index;
|
||||||
|
barrier.dst_queue_family_index = dst_queue_family_index;
|
||||||
|
barrier.image = image;
|
||||||
|
barrier.subresource_range.aspect_mask = vk::ImageAspectFlags::COLOR;
|
||||||
|
barrier.subresource_range.base_array_layer = 0;
|
||||||
|
barrier.subresource_range.level_count = levels;
|
||||||
|
barrier.subresource_range.layer_count = vk::REMAINING_ARRAY_LAYERS;
|
||||||
|
device.cmd_pipeline_barrier(
|
||||||
|
cmd,
|
||||||
|
src_stage,
|
||||||
|
dst_stage,
|
||||||
|
vk::DependencyFlags::empty(),
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
&[barrier],
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue