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.
|
||||
pub struct VulkanObjects {
|
||||
pub(crate) device: Arc<ash::Device>,
|
||||
pub(crate) alloc: Arc<Mutex<Allocator>>,
|
||||
queue: vk::Queue,
|
||||
/// The handle to the initialized `ash::Device`
|
||||
pub device: Arc<ash::Device>,
|
||||
/// 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.
|
||||
|
|
|
@ -16,6 +16,10 @@ ash = "0.38.0+1.3.281"
|
|||
pollster = "0.3.0"
|
||||
parking_lot = "0.12.3"
|
||||
image-compare = "0.4.1"
|
||||
gpu-allocator = "0.27.0"
|
||||
|
||||
[features]
|
||||
vulkan-debug = []
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
workspace = true
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
|
||||
/// Render tests
|
||||
pub mod render;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod d3d11;
|
||||
pub mod vk;
|
||||
pub mod wgpu;
|
||||
|
||||
use std::path::Path;
|
||||
|
@ -27,7 +28,10 @@ mod test {
|
|||
use std::fs::File;
|
||||
|
||||
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]
|
||||
pub fn test_d3d11() -> anyhow::Result<()> {
|
||||
|
@ -49,6 +53,16 @@ mod test {
|
|||
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]
|
||||
pub fn compare() -> anyhow::Result<()> {
|
||||
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