use std::{borrow::Cow, path::Path, sync::Arc}; use ash::{ext, prelude::*, vk}; use parking_lot::Mutex; use crate::{ device::{ Device, DeviceHandle, DeviceInner, DeviceObject, asdf::traits::ExternallyManagedObject, }, make_extension, }; #[derive(Debug)] pub struct ShaderStageDesc<'a> { pub flags: vk::PipelineShaderStageCreateFlags, pub module: &'a ShaderModule, pub stage: vk::ShaderStageFlags, pub entry: Cow<'a, std::ffi::CStr>, // specialization: Option } #[derive(Debug, Default)] pub struct DescriptorSetLayoutBindingDesc { pub binding: u32, pub count: u32, pub kind: vk::DescriptorType, pub stage: vk::ShaderStageFlags, pub flags: Option, } #[derive(Debug, Default)] pub struct DescriptorSetLayoutDesc<'a> { pub flags: vk::DescriptorSetLayoutCreateFlags, pub bindings: &'a [DescriptorSetLayoutBindingDesc], pub name: Option>, } #[derive(Debug, Default)] pub struct PipelineLayoutDesc<'a> { pub descriptor_set_layouts: &'a [&'a DescriptorSetLayout], pub push_constant_ranges: &'a [vk::PushConstantRange], pub name: Option>, } #[derive(Debug)] pub struct ComputePipelineDesc<'a> { pub flags: vk::PipelineCreateFlags, pub name: Option>, pub shader_stage: ShaderStageDesc<'a>, pub layout: Arc, pub base_pipeline: Option>, } #[derive(Debug, Default)] pub struct VertexInputState<'a> { // pub flags: vk::PipelineVertexInputStateCreateFlags, pub bindings: &'a [vk::VertexInputBindingDescription], pub attributes: &'a [vk::VertexInputAttributeDescription], } #[derive(Debug, Default)] pub struct TessellationState { pub flags: vk::PipelineTessellationStateCreateFlags, pub patch_control_points: u32, } #[derive(Debug, Default)] pub struct InputAssemblyState { // pub flags: vk::PipelineInputAssemblyStateCreateFlags, pub topology: vk::PrimitiveTopology, pub primitive_restart: bool, } #[derive(Debug, Default)] pub struct ViewportState<'a> { pub num_scissors: u32, pub scissors: Option<&'a [vk::Rect2D]>, pub num_viewports: u32, pub viewports: Option<&'a [vk::Viewport]>, } #[derive(Debug, Default)] pub struct DepthBiasState { pub clamp: f32, pub constant_factor: f32, pub slope_factor: f32, } #[derive(Debug)] pub struct RasterizationState { pub depth_clamp_enable: bool, pub discard_enable: bool, pub line_width: f32, pub cull_mode: vk::CullModeFlags, pub depth_bias: Option, pub polygon_mode: vk::PolygonMode, } impl Default for RasterizationState { fn default() -> Self { Self { depth_clamp_enable: false, line_width: 1.0, cull_mode: vk::CullModeFlags::BACK, depth_bias: Default::default(), polygon_mode: vk::PolygonMode::FILL, discard_enable: false, } } } #[derive(Debug)] pub struct MultisampleState<'a> { pub flags: vk::PipelineMultisampleStateCreateFlags, pub sample_shading_enable: bool, pub rasterization_samples: vk::SampleCountFlags, pub min_sample_shading: f32, pub sample_mask: &'a [vk::SampleMask], pub alpha_to_coverage_enable: bool, pub alpha_to_one_enable: bool, } impl<'a> Default for MultisampleState<'a> { fn default() -> Self { Self { flags: Default::default(), sample_shading_enable: Default::default(), rasterization_samples: vk::SampleCountFlags::TYPE_1, min_sample_shading: 1.0, sample_mask: Default::default(), alpha_to_coverage_enable: Default::default(), alpha_to_one_enable: Default::default(), } } } #[derive(Debug)] pub struct DepthBounds { pub min: f32, pub max: f32, } #[derive(Debug, Default)] pub struct DepthState { pub write_enable: bool, /// sets depthTestEnable to true when `Some` pub compare_op: Option, /// sets depthBoundsTestEnable to true when `Some` pub bounds: Option, } #[derive(Debug, Default)] pub struct StencilState { pub front: vk::StencilOpState, pub back: vk::StencilOpState, } #[derive(Debug, Default)] pub struct DepthStencilState { pub flags: vk::PipelineDepthStencilStateCreateFlags, pub depth: Option, pub stencil: Option, } #[derive(Debug, Default)] pub struct ColorBlendState<'a> { pub flags: vk::PipelineColorBlendStateCreateFlags, pub attachments: &'a [vk::PipelineColorBlendAttachmentState], pub logic_op: Option, pub blend_constants: [f32; 4], } #[derive(Debug, Default)] pub struct RenderingState<'a> { pub color_formats: &'a [vk::Format], pub depth_format: Option, pub stencil_format: Option, } #[derive(Debug, Default)] pub struct DynamicState<'a> { pub flags: vk::PipelineDynamicStateCreateFlags, pub dynamic_states: &'a [vk::DynamicState], } #[derive(Debug)] pub struct GraphicsPipelineDesc<'a> { pub flags: vk::PipelineCreateFlags, pub name: Option>, pub shader_stages: &'a [ShaderStageDesc<'a>], pub render_pass: Option, pub layout: &'a PipelineLayout, pub subpass: Option, pub base_pipeline: Option>, pub vertex_input: Option>, pub input_assembly: Option, pub tessellation: Option, pub viewport: Option>, pub rasterization: Option, pub multisample: Option>, pub depth_stencil: Option, pub color_blend: Option>, pub dynamic: Option>, pub rendering: Option>, } #[derive(Debug, Default)] pub struct DescriptorPoolDesc<'a> { pub flags: vk::DescriptorPoolCreateFlags, pub name: Option>, pub sizes: &'a [vk::DescriptorPoolSize], pub max_sets: u32, } #[derive(Debug)] pub struct DescriptorSetAllocDesc<'a> { pub name: Option>, pub layout: &'a DescriptorSetLayout, } impl DeviceHandle for vk::DescriptorPool { unsafe fn destroy(&mut self, device: &Device) { unsafe { device.dev().destroy_descriptor_pool(*self, None) }; } } impl> ExternallyManagedObject for vk::DescriptorPool { unsafe fn destroy(self, device: &T) { // SAFETY: We have exclusive ownership of the descriptor pool, so it's safe to destroy it. unsafe { device.as_ref().raw.destroy_descriptor_pool(self, None); } } } #[derive(Debug)] pub struct DescriptorPool { pool: DeviceObject, lock: Mutex<()>, } impl DescriptorPool { pub fn new(device: Device, desc: DescriptorPoolDesc) -> crate::Result { let info = &vk::DescriptorPoolCreateInfo::default() .flags(desc.flags) .max_sets(desc.max_sets) .pool_sizes(desc.sizes); let handle = unsafe { device.dev().create_descriptor_pool(info, None)? }; Ok(Self { pool: DeviceObject::new_debug_named(device, handle, desc.name), lock: Mutex::new(()), }) } pub fn allocate(&self, descs: &[DescriptorSetAllocDesc]) -> VkResult> { let layouts = descs .iter() .map(|desc| desc.layout.raw()) .collect::>(); let info = &vk::DescriptorSetAllocateInfo::default() .descriptor_pool(*self.pool) .set_layouts(&layouts); let sets = unsafe { let _lock = self.lock.lock(); self.pool.device().raw.allocate_descriptor_sets(info)? }; for (&set, desc) in sets.iter().zip(descs) { if let Some(name) = desc.name.as_ref() { unsafe { self.pool.device().debug_name_object(set, name) }; } } Ok(sets) } #[allow(dead_code)] pub fn reset(&self) -> VkResult<()> { let _lock = self.lock.lock(); unsafe { self.pool .device() .raw .reset_descriptor_pool(*self.pool, vk::DescriptorPoolResetFlags::empty()) } } } impl> ExternallyManagedObject for vk::DescriptorSetLayout { unsafe fn destroy(self, device: &T) { unsafe { device .as_ref() .raw .destroy_descriptor_set_layout(self, None); } } } #[derive(Debug)] pub struct DescriptorSetLayout { layout: DeviceObject, } impl DescriptorSetLayout { pub fn new(device: Device, desc: DescriptorSetLayoutDesc) -> crate::Result { let (flags, bindings): (Vec<_>, Vec<_>) = desc .bindings .iter() .map(|binding| { let flag = binding.flags.unwrap_or_default(); let binding = vk::DescriptorSetLayoutBinding::default() .binding(binding.binding) .descriptor_count(binding.count) .descriptor_type(binding.kind) .stage_flags(binding.stage); (flag, binding) }) .unzip(); let flags = &mut vk::DescriptorSetLayoutBindingFlagsCreateInfo::default().binding_flags(&flags); let mut info = vk::DescriptorSetLayoutCreateInfo::default() .bindings(&bindings) .flags(desc.flags); if device.properties().device_api_version >= vk::API_VERSION_1_2 || device .properties() .supports_extension(make_extension!(ext::descriptor_indexing)) { info = info.push_next(flags); } let layout = unsafe { device.raw.create_descriptor_set_layout(&info, None)? }; Ok(Self { layout: DeviceObject::new_debug_named(device, layout, desc.name), }) } pub fn raw(&self) -> vk::DescriptorSetLayout { *self.layout } } use crate::device::DeviceOwned; impl> ExternallyManagedObject for vk::PipelineLayout { unsafe fn destroy(self, device: &T) { unsafe { device.as_ref().raw.destroy_pipeline_layout(self, None); } } } #[derive(Debug)] pub struct PipelineLayout { layout: DeviceObject, } impl PipelineLayout { pub fn new(device: Device, desc: PipelineLayoutDesc) -> crate::Result { let set_layouts = desc .descriptor_set_layouts .iter() .map(|desc| desc.raw()) .collect::>(); let info = &vk::PipelineLayoutCreateInfo::default() .set_layouts(&set_layouts) .push_constant_ranges(desc.push_constant_ranges); let layout = unsafe { device.raw.create_pipeline_layout(info, None)? }; Ok(Self { layout: DeviceObject::new_debug_named(device, layout, desc.name), }) } pub fn raw(&self) -> vk::PipelineLayout { *self.layout } } #[derive(Debug, Default)] pub struct SamplerDesc { pub flags: vk::SamplerCreateFlags, pub min_filter: vk::Filter, pub mag_filter: vk::Filter, pub mipmap_mode: vk::SamplerMipmapMode, pub address_u: vk::SamplerAddressMode, pub address_v: vk::SamplerAddressMode, pub address_w: vk::SamplerAddressMode, pub mip_lod_bias: f32, pub anisotropy_enable: bool, pub max_anisotropy: f32, pub compare_op: Option, pub min_lod: f32, pub max_lod: f32, pub border_color: vk::BorderColor, pub unnormalized_coordinates: bool, } impl Eq for SamplerDesc {} impl PartialEq for SamplerDesc { fn eq(&self, other: &Self) -> bool { use crate::util::eq_f32; self.flags == other.flags && self.min_filter == other.min_filter && self.mag_filter == other.mag_filter && self.mipmap_mode == other.mipmap_mode && self.address_u == other.address_u && self.address_v == other.address_v && self.address_w == other.address_w && self.anisotropy_enable == other.anisotropy_enable && self.compare_op == other.compare_op && eq_f32(self.mip_lod_bias, other.mip_lod_bias) && eq_f32(self.max_anisotropy, other.max_anisotropy) && eq_f32(self.min_lod, other.min_lod) && eq_f32(self.max_lod, other.max_lod) && self.border_color == other.border_color && self.unnormalized_coordinates == other.unnormalized_coordinates } } impl std::hash::Hash for SamplerDesc { fn hash(&self, state: &mut H) { use crate::util::hash_f32; self.flags.hash(state); self.min_filter.hash(state); self.mag_filter.hash(state); self.mipmap_mode.hash(state); self.address_u.hash(state); self.address_v.hash(state); self.address_w.hash(state); hash_f32(state, self.mip_lod_bias); hash_f32(state, self.max_anisotropy); hash_f32(state, self.min_lod); hash_f32(state, self.max_lod); self.anisotropy_enable.hash(state); self.compare_op.hash(state); self.border_color.hash(state); self.unnormalized_coordinates.hash(state); } } impl ExternallyManagedObject for vk::Sampler where T: AsRef, { unsafe fn destroy(self, device: &T) { unsafe { device.as_ref().raw.destroy_sampler(self, None); } } } #[derive(Debug)] pub struct Sampler { sampler: DeviceObject, } impl Sampler { pub fn new(device: Device, desc: &SamplerDesc) -> VkResult { let info = &vk::SamplerCreateInfo::default() .flags(desc.flags) .min_filter(desc.min_filter) .mag_filter(desc.mag_filter) .mip_lod_bias(desc.mip_lod_bias) .mipmap_mode(desc.mipmap_mode) .address_mode_u(desc.address_u) .address_mode_v(desc.address_v) .address_mode_w(desc.address_w) .anisotropy_enable(desc.anisotropy_enable) .max_anisotropy(desc.max_anisotropy) .compare_enable(desc.compare_op.is_some()) .compare_op(desc.compare_op.unwrap_or_default()) .min_lod(desc.min_lod) .max_lod(desc.max_lod) .border_color(desc.border_color) .unnormalized_coordinates(desc.unnormalized_coordinates); let handle = unsafe { device.dev().create_sampler(info, None)? }; Ok(Self { sampler: DeviceObject::new(device, handle), }) } } impl> ExternallyManagedObject for vk::ShaderModule { unsafe fn destroy(self, device: &T) { unsafe { device.as_ref().raw.destroy_shader_module(self, None); } } } #[derive(Debug)] pub struct ShaderModule { module: DeviceObject, } impl ShaderModule { pub fn raw(&self) -> vk::ShaderModule { *self.module } pub fn new_from_path>(device: Device, path: P) -> crate::Result { use std::io::{BufReader, Read, Seek}; let path = path.as_ref(); let mut file = std::fs::File::open(path).map_err(|_| crate::Error::AssetMissing(path.to_owned()))?; let size = file.seek(std::io::SeekFrom::End(0))? / 4; file.seek(std::io::SeekFrom::Start(0))?; let mut reader = BufReader::new(file); let mut buffer = vec![0; size as usize]; let size = reader.read(bytemuck::cast_slice_mut(buffer.as_mut_slice()))?; buffer.resize(size / 4, 0); Self::new_from_memory(device, &buffer) } pub fn new_from_memory(device: Device, buffer: &[u32]) -> crate::Result { let info = &vk::ShaderModuleCreateInfo::default().code(buffer); let module = unsafe { device.dev().create_shader_module(info, None)? }; Ok(Self { module: DeviceObject::new(device, module), }) } } #[derive(Debug)] pub struct Pipeline { pipeline: DeviceObject, bind_point: vk::PipelineBindPoint, } impl> ExternallyManagedObject for vk::Pipeline { unsafe fn destroy(self, device: &T) { unsafe { device.as_ref().raw.destroy_pipeline(self, None); } } } impl ShaderStageDesc<'_> { fn as_create_info(&'_ self) -> vk::PipelineShaderStageCreateInfo<'_> { vk::PipelineShaderStageCreateInfo::default() .module(self.module.raw()) .flags(self.flags) .stage(self.stage) .name(&self.entry) } } impl Pipeline { pub fn new_compute(device: Device, desc: ComputePipelineDesc) -> crate::Result { let info = &vk::ComputePipelineCreateInfo::default() .flags(desc.flags) .layout(desc.layout.raw()) .base_pipeline_handle( desc.base_pipeline .map(|p| p.raw()) .unwrap_or(vk::Pipeline::null()), ) .stage(desc.shader_stage.as_create_info()); let pipeline = unsafe { device .dev() .create_compute_pipelines( device.pools.pipeline_cache.raw, core::slice::from_ref(info), None, ) // It's cool to just take the first one and ignore any // potentially created pipelines since we know there wont be any // others. .map_err(|(_, err)| err)?[0] }; Ok(Self { pipeline: DeviceObject::new_debug_named(device, pipeline, desc.name), bind_point: vk::PipelineBindPoint::COMPUTE, }) } pub fn new_graphics(device: Device, desc: GraphicsPipelineDesc) -> crate::Result { let stages = desc .shader_stages .iter() .map(|stage| stage.as_create_info()) .collect::>(); let vertex_input = desc.vertex_input.map(|vertex| { vk::PipelineVertexInputStateCreateInfo::default() .vertex_attribute_descriptions(vertex.attributes) .vertex_binding_descriptions(vertex.bindings) }); let input_assembly = desc.input_assembly.map(|state| { vk::PipelineInputAssemblyStateCreateInfo::default() .primitive_restart_enable(state.primitive_restart) .topology(state.topology) }); let tessellation = desc.tessellation.map(|state| { vk::PipelineTessellationStateCreateInfo::default() .flags(state.flags) .patch_control_points(state.patch_control_points) }); let viewport = desc.viewport.map(|state| { let mut info = vk::PipelineViewportStateCreateInfo::default() .scissor_count(state.num_scissors) .viewport_count(state.num_viewports); if let Some(viewports) = state.viewports { info = info.viewports(viewports); } if let Some(scissors) = state.scissors { info = info.scissors(scissors); } info }); let rasterization = desc.rasterization.map(|state| { let mut info = vk::PipelineRasterizationStateCreateInfo::default() .line_width(state.line_width) .cull_mode(state.cull_mode) .polygon_mode(state.polygon_mode) .rasterizer_discard_enable(state.discard_enable) .depth_clamp_enable(state.depth_clamp_enable); if let Some(depth_bias) = state.depth_bias { info = info .depth_bias_enable(true) .depth_bias_clamp(depth_bias.clamp) .depth_bias_constant_factor(depth_bias.constant_factor) .depth_bias_slope_factor(depth_bias.slope_factor); } info }); let multisample = desc.multisample.map(|state| { vk::PipelineMultisampleStateCreateInfo::default() .flags(state.flags) .min_sample_shading(state.min_sample_shading) .rasterization_samples(state.rasterization_samples) .sample_mask(state.sample_mask) .sample_shading_enable(state.sample_shading_enable) .alpha_to_coverage_enable(state.alpha_to_coverage_enable) .alpha_to_one_enable(state.alpha_to_one_enable) }); let color_blend = desc.color_blend.map(|state| { vk::PipelineColorBlendStateCreateInfo::default() .flags(state.flags) .attachments(state.attachments) .blend_constants(state.blend_constants) .logic_op(state.logic_op.unwrap_or(Default::default())) .logic_op_enable(state.logic_op.is_some()) }); let depth_stencil = desc.depth_stencil.map(|state| { let mut info = vk::PipelineDepthStencilStateCreateInfo::default().flags(state.flags); if let Some(depth) = state.depth { info = info .depth_compare_op(depth.compare_op.unwrap_or(vk::CompareOp::default())) .depth_test_enable(depth.compare_op.is_some()) .depth_write_enable(depth.write_enable) .depth_bounds_test_enable(depth.bounds.is_some()); if let Some(bounds) = depth.bounds { info = info .max_depth_bounds(bounds.max) .min_depth_bounds(bounds.min); } } if let Some(stencil) = state.stencil { info = info .stencil_test_enable(true) .front(stencil.front) .back(stencil.back); } info }); let dynamic = desc.dynamic.map(|state| { vk::PipelineDynamicStateCreateInfo::default() .flags(state.flags) .dynamic_states(state.dynamic_states) }); let mut rendering = desc.rendering.map(|state| { vk::PipelineRenderingCreateInfo::default() .color_attachment_formats(state.color_formats) .depth_attachment_format(state.depth_format.unwrap_or_default()) .stencil_attachment_format(state.stencil_format.unwrap_or_default()) }); fn option_to_ptr(option: &Option) -> *const T { option .as_ref() .map(|t| t as *const T) .unwrap_or(core::ptr::null()) } let mut info = vk::GraphicsPipelineCreateInfo { flags: desc.flags, stage_count: stages.len() as u32, p_stages: stages.as_ptr(), p_vertex_input_state: option_to_ptr(&vertex_input), p_input_assembly_state: option_to_ptr(&input_assembly), p_tessellation_state: option_to_ptr(&tessellation), p_viewport_state: option_to_ptr(&viewport), p_rasterization_state: option_to_ptr(&rasterization), p_multisample_state: option_to_ptr(&multisample), p_depth_stencil_state: option_to_ptr(&depth_stencil), p_color_blend_state: option_to_ptr(&color_blend), p_dynamic_state: option_to_ptr(&dynamic), layout: desc.layout.raw(), render_pass: desc.render_pass.unwrap_or(vk::RenderPass::null()), subpass: desc.subpass.unwrap_or(0), base_pipeline_handle: desc .base_pipeline .map(|piepline| *piepline.pipeline) .unwrap_or(vk::Pipeline::null()), ..Default::default() }; if let Some(rendering) = rendering.as_mut() { info = info.push_next(rendering) } let pipeline = unsafe { device .dev() .create_graphics_pipelines( device.pools.pipeline_cache.raw, core::slice::from_ref(&info), None, ) // It's cool to just take the first one and ignore any // potentially created pipelines since we know there wont be any // others. .map_err(|(_, err)| err)?[0] }; Ok(Self { pipeline: DeviceObject::new_debug_named(device, pipeline, desc.name), bind_point: vk::PipelineBindPoint::GRAPHICS, }) } pub fn raw(&self) -> vk::Pipeline { *self.pipeline } pub fn bind_point(&self) -> vk::PipelineBindPoint { self.bind_point } } pub(crate) mod pipeline_cache { use std::sync::Arc; use ash::vk; use ash::Device; use crate::PhysicalDeviceInfo; use crate::device::DeviceInner; #[derive(Debug)] pub struct PipelineCache { #[allow(dead_code)] key: u128, pub(crate) raw: vk::PipelineCache, } impl crate::device::asdf::traits::ExternallyManagedObject> for PipelineCache { unsafe fn destroy(self, owner: &Arc) { tracing::info!("destroying pipeline cache with key {:x}", self.key); if let Ok(data) = self.export(&owner.raw) { tracing::info!( "exported pipeline cache with key {:x} and size {} bytes", self.key, data.len() ); _ = Self::write_to_disk(self.key, &data).inspect_err(|err| { tracing::error!("failed to write pipeline cache to disk: {err}"); }); } unsafe { owner.raw.destroy_pipeline_cache(self.raw, None) }; } } impl PipelineCache { const MAGIC: [u8; 4] = *b"VYPC"; const KEY_VERSION: u32 = 1; const PATH: &'static str = "pipeline_cache.bin"; fn calculate_key(adapter: &PhysicalDeviceInfo) -> u128 { use md5::Digest; let mut hasher = md5::Md5::new(); let props = &adapter.properties; hasher.update(bytemuck::bytes_of(&[ props.core.vendor_id, props.core.api_version, props.core.device_id, props.core.driver_version, ])); u128::from_le_bytes(hasher.finalize().into()) } fn load_from_disk(key: u128) -> Option<(u128, Vec)> { use std::io::Read; let file = std::fs::File::open(Self::PATH).ok()?; let mut reader = std::io::BufReader::new(file); let mut magic = [0; 4]; reader.read_exact(&mut magic).ok()?; if magic != Self::MAGIC { return None; } let mut version = 0; reader .read_exact(bytemuck::bytes_of_mut(&mut version)) .ok()?; if version != Self::KEY_VERSION { return None; } let mut disk_key = 0; reader .read_exact(bytemuck::bytes_of_mut(&mut disk_key)) .ok()?; if disk_key != key { return None; } let mut data = Vec::new(); reader.read_to_end(&mut data).ok()?; Some((key, data)) } fn write_to_disk(key: u128, data: &[u8]) -> std::io::Result<()> { use std::io::Write; let file = std::fs::File::create(Self::PATH)?; let mut writer = std::io::BufWriter::new(file); writer.write_all(&Self::MAGIC)?; writer.write_all(bytemuck::bytes_of(&Self::KEY_VERSION))?; writer.write_all(bytemuck::bytes_of(&key))?; writer.write_all(data)?; Ok(()) } pub fn new(device: &Device, adapter: &PhysicalDeviceInfo) -> crate::Result { let key = Self::calculate_key(adapter); let data = Self::load_from_disk(key).map(|(key, data)| { tracing::info!("loaded pipeline cache from disk with key {key:x}"); data }); let info = vk::PipelineCacheCreateInfo::default() // .flags(vk::PipelineCacheCreateFlags::EXTERNALLY_SYNCHRONIZED) .initial_data(data.as_deref().unwrap_or_default()); let cache = unsafe { device.create_pipeline_cache(&info, None)? }; Ok(Self { key, raw: cache }) } pub fn export(&self, device: &ash::Device) -> crate::Result> { let data = unsafe { device.get_pipeline_cache_data(self.raw)? }; Ok(data) } } }