diff --git a/librashader-runtime-vk/src/filter_chain.rs b/librashader-runtime-vk/src/filter_chain.rs index af0b7df..ab502a0 100644 --- a/librashader-runtime-vk/src/filter_chain.rs +++ b/librashader-runtime-vk/src/filter_chain.rs @@ -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, - pub(crate) alloc: Arc>, - queue: vk::Queue, + /// The handle to the initialized `ash::Device` + pub device: Arc, + /// The instance of the `gpu-allocator` to use. + pub alloc: Arc>, + /// The graphics queue to do work on. + pub queue: vk::Queue, } /// A collection of handles needed to access the Vulkan instance. diff --git a/librashader-test/Cargo.toml b/librashader-test/Cargo.toml index e592294..f3a9a76 100644 --- a/librashader-test/Cargo.toml +++ b/librashader-test/Cargo.toml @@ -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 diff --git a/librashader-test/src/lib.rs b/librashader-test/src/lib.rs index 2abe71c..bbc93ca 100644 --- a/librashader-test/src/lib.rs +++ b/librashader-test/src/lib.rs @@ -1,3 +1,2 @@ - /// Render tests pub mod render; diff --git a/librashader-test/src/render/mod.rs b/librashader-test/src/render/mod.rs index 362cb46..1ec2a8a 100644 --- a/librashader-test/src/render/mod.rs +++ b/librashader-test/src/render/mod.rs @@ -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)?; diff --git a/librashader-test/src/render/vk/base.rs b/librashader-test/src/render/vk/base.rs new file mode 100644 index 0000000..4d88742 --- /dev/null +++ b/librashader-test/src/render/vk/base.rs @@ -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, + graphics_queue: vk::Queue, + // pub debug: VulkanDebug, + physical_device: vk::PhysicalDevice, + mem_props: vk::PhysicalDeviceMemoryProperties, + allocator: Arc>, + _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 { + 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 { + &self.device + } + pub(crate) fn allocator(&self) -> &Arc> { + &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(&self, f: impl FnOnce(vk::CommandBuffer) -> T) -> anyhow::Result { + 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); + } + } +} diff --git a/librashader-test/src/render/vk/memory.rs b/librashader-test/src/render/vk/memory.rs new file mode 100644 index 0000000..afd30e3 --- /dev/null +++ b/librashader-test/src/render/vk/memory.rs @@ -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, + allocator: Arc>, +} + +impl VulkanImageMemory { + pub fn new( + device: &Arc, + allocator: &Arc>, + requirements: vk::MemoryRequirements, + image: &vk::Image, + location: MemoryLocation, + ) -> anyhow::Result { + 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, + allocation: ManuallyDrop, + allocator: Arc>, + size: vk::DeviceSize, +} + +impl VulkanBuffer { + pub fn new( + device: &Arc, + allocator: &Arc>, + usage: vk::BufferUsageFlags, + size: usize, + ) -> anyhow::Result { + 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> { + 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)) +} diff --git a/librashader-test/src/render/vk/mod.rs b/librashader-test/src/render/vk/mod.rs new file mode 100644 index 0000000..80220aa --- /dev/null +++ b/librashader-test/src/render/vk/mod.rs @@ -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, + image: vk::Image, + view: vk::ImageView, + image_alloc: VulkanImageMemory, +} + +impl RenderTest for Vulkan { + fn render(&self, path: impl AsRef, frame_count: usize) -> anyhow::Result { + 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) -> anyhow::Result { + 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, + ) -> anyhow::Result<(Image, VulkanImageMemory, vk::Image, vk::ImageView)> { + let image: Image = 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)) + } +} diff --git a/librashader-test/src/render/vk/physical_device.rs b/librashader-test/src/render/vk/physical_device.rs new file mode 100644 index 0000000..662891b --- /dev/null +++ b/librashader-test/src/render/vk/physical_device.rs @@ -0,0 +1,144 @@ +use anyhow::anyhow; +use ash::vk; +use std::ffi::CStr; + +pub struct QueueFamilyIndices { + graphics_family: Option, +} + +impl QueueFamilyIndices { + pub fn is_complete(&self) -> bool { + self.graphics_family.is_some() + } + + pub fn graphics_family(&self) -> anyhow::Result { + self.graphics_family + .ok_or(anyhow!("The queue family is not complete")) + } +} + +pub(crate) fn pick_physical_device(instance: &ash::Instance) -> anyhow::Result { + 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 +} diff --git a/librashader-test/src/render/vk/util.rs b/librashader-test/src/render/vk/util.rs new file mode 100644 index 0000000..8c8c1d3 --- /dev/null +++ b/librashader-test/src/render/vk/util.rs @@ -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], + ) +}