Compare commits

..

No commits in common. "7c3f7120b03a26bcc818be08485202bd8242b50a" and "0529ce0a3c3faffcb35ecc1f055dd0e687f14ece" have entirely different histories.

8 changed files with 389 additions and 742 deletions

View file

@ -52,9 +52,6 @@ petgraph = "0.7"
itertools = "0.14.0" itertools = "0.14.0"
ahash = "0.8" ahash = "0.8"
# for non-cryptographic hashing of resources like pipelines, e.g. for caching
md-5 = "0.11.0"
parking_lot = "0.12.3" parking_lot = "0.12.3"
tokio = "1.42" tokio = "1.42"

View file

@ -26,8 +26,6 @@ vk-mem = { workspace = true }
gpu-allocator = { workspace = true } gpu-allocator = { workspace = true }
rectangle-pack = { workspace = true } rectangle-pack = { workspace = true }
md-5 = { workspace = true }
raw-window-handle = { workspace = true } raw-window-handle = { workspace = true }
egui = { workspace = true , features = ["bytemuck"]} egui = { workspace = true , features = ["bytemuck"]}
egui_winit_platform = { workspace = true } egui_winit_platform = { workspace = true }

View file

@ -547,7 +547,7 @@ pub mod traits {
self.device().dev().cmd_bind_pipeline( self.device().dev().cmd_bind_pipeline(
self.handle(), self.handle(),
pipeline.bind_point(), pipeline.bind_point(),
pipeline.raw(), pipeline.handle(),
); );
} }
} }

View file

@ -2,6 +2,7 @@ use std::{
borrow::Cow, borrow::Cow,
collections::{BTreeSet, HashMap, HashSet}, collections::{BTreeSet, HashMap, HashSet},
ffi::CStr, ffi::CStr,
mem::ManuallyDrop,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::Arc, sync::Arc,
}; };
@ -16,7 +17,6 @@ use raw_window_handle::RawDisplayHandle;
use crate::{ use crate::{
Instance, PhysicalDeviceFeatures, PhysicalDeviceInfo, Result, Instance, PhysicalDeviceFeatures, PhysicalDeviceInfo, Result,
pipeline::pipeline_cache::PipelineCache,
queue::{DeviceQueueInfos, DeviceQueues, Queue}, queue::{DeviceQueueInfos, DeviceQueues, Queue},
sync::{self, BinarySemaphore, TimelineSemaphore}, sync::{self, BinarySemaphore, TimelineSemaphore},
}; };
@ -109,16 +109,9 @@ pub struct DeviceInner {
pub(crate) device_extensions: DeviceExtensions, pub(crate) device_extensions: DeviceExtensions,
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) enabled_extensions: Vec<&'static CStr>, pub(crate) enabled_extensions: Vec<&'static CStr>,
_drop: DeviceDrop, _drop: DeviceDrop,
} }
impl AsRef<DeviceInner> for DeviceInner {
fn as_ref(&self) -> &DeviceInner {
self
}
}
impl core::fmt::Debug for DeviceInner { impl core::fmt::Debug for DeviceInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeviceInner") f.debug_struct("DeviceInner")
@ -444,30 +437,19 @@ impl PhysicalDeviceInfo {
} }
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub(crate) struct DevicePools { pub(crate) struct DevicePools {
pub(crate) fences: Arc<Pool<vk::Fence>>, pub(crate) fences: Pool<vk::Fence>,
pub(crate) binary_semaphores: Pool<BinarySemaphore>, pub(crate) binary_semaphores: Pool<BinarySemaphore>,
pub(crate) timeline_semaphores: Pool<TimelineSemaphore>, pub(crate) timeline_semaphores: Pool<TimelineSemaphore>,
pub(crate) pipeline_cache: asdf::DeviceObject<PipelineCache, Arc<DeviceInner>>,
}
impl AsRef<DevicePools> for DevicePools {
fn as_ref(&self) -> &DevicePools {
self
}
} }
impl DevicePools { impl DevicePools {
pub fn new(device: Arc<DeviceInner>) -> Self { pub fn new(device: Arc<DeviceInner>) -> Self {
Self { Self {
fences: Arc::new(Pool::new(device.clone())), fences: Pool::new(device.clone()),
binary_semaphores: Pool::new(device.clone()), binary_semaphores: Pool::new(device.clone()),
timeline_semaphores: Pool::new(device.clone()), timeline_semaphores: Pool::new(device),
pipeline_cache: asdf::DeviceObject::new(
device.clone(),
PipelineCache::new(&device.raw, &device.adapter).unwrap(),
),
} }
} }
} }
@ -494,16 +476,6 @@ impl core::ops::Deref for Device {
} }
} }
impl<T> AsRef<T> for Device
where
T: ?Sized,
<Self as Deref>::Target: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.deref().as_ref()
}
}
impl DeviceInner { impl DeviceInner {
pub fn sync_threadpool(&self) -> &sync::SyncThreadpool { pub fn sync_threadpool(&self) -> &sync::SyncThreadpool {
&self.sync_threadpool &self.sync_threadpool
@ -729,7 +701,7 @@ impl<T: DeviceHandle> DeviceObject<T> {
{ {
unsafe { unsafe {
if let Some(name) = name.as_ref() { if let Some(name) = name.as_ref() {
device.debug_name_object(inner.clone(), name); device.debug_name_object(inner.clone(), &name);
} }
} }
@ -754,7 +726,7 @@ impl<T: DeviceHandle> DeviceObject<T> {
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
self.name.as_deref() self.name.as_deref().map(|cow| cow.as_ref())
} }
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
{ {
@ -772,8 +744,6 @@ impl<T: DeviceHandle> Drop for DeviceObject<T> {
} }
pub trait DeviceHandle { pub trait DeviceHandle {
/// # Safety
/// The caller must ensure this function is only called once for a given object.
unsafe fn destroy(&mut self, device: &Device); unsafe fn destroy(&mut self, device: &Device);
} }
@ -804,9 +774,11 @@ impl DeviceHandle for vk::Buffer {
impl DeviceHandle for vk::SwapchainKHR { impl DeviceHandle for vk::SwapchainKHR {
unsafe fn destroy(&mut self, device: &Device) { unsafe fn destroy(&mut self, device: &Device) {
unsafe { unsafe {
if let Some(swapchain) = device.device_extensions.swapchain.as_ref() { device
swapchain.destroy_swapchain(*self, None) .device_extensions
} .swapchain
.as_ref()
.map(|swapchain| swapchain.destroy_swapchain(*self, None));
} }
} }
} }
@ -820,9 +792,52 @@ pub trait Pooled: Sized {
fn create_from_pool(pool: &Pool<Self>) -> Result<Self>; fn create_from_pool(pool: &Pool<Self>) -> Result<Self>;
} }
#[derive(Debug)] pub struct PoolObject<T: Pooled + vk::Handle + Clone> {
pub(crate) inner: ManuallyDrop<T>,
pub(crate) pool: Pool<T>,
#[cfg(debug_assertions)]
pub(crate) name: Option<Cow<'static, str>>,
}
impl<T: Pooled + vk::Handle + Clone> PoolObject<T> {
pub fn name_object(&mut self, name: impl Into<Cow<'static, str>>) {
#[cfg(debug_assertions)]
unsafe {
self.name = Some(name.into());
self.pool
.device
.debug_name_object(T::clone(&self.inner), self.name.as_ref().unwrap());
}
}
pub fn device(&self) -> &Arc<DeviceInner> {
&self.pool.device
}
}
impl<T: Pooled + vk::Handle + Clone> Drop for PoolObject<T> {
fn drop(&mut self) {
let handle = unsafe { ManuallyDrop::take(&mut self.inner) };
#[cfg(debug_assertions)]
if self.name.is_some() {
unsafe { self.pool.device.debug_name_object(handle.clone(), "") };
}
self.pool.push(handle);
}
}
impl<T: Pooled + vk::Handle + Clone> Deref for PoolObject<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug, Clone)]
pub struct Pool<T> { pub struct Pool<T> {
pub(crate) pool: Mutex<Vec<T>>, pub(crate) pool: Arc<Mutex<Vec<T>>>,
pub(crate) device: Arc<DeviceInner>, pub(crate) device: Arc<DeviceInner>,
} }
@ -832,7 +847,7 @@ impl<T> Pool<T> {
} }
pub fn new(device: Arc<DeviceInner>) -> Self { pub fn new(device: Arc<DeviceInner>) -> Self {
Self { Self {
pool: Mutex::new(Vec::new()), pool: Arc::new(Mutex::new(Vec::new())),
device, device,
} }
} }
@ -841,37 +856,27 @@ impl<T> Pool<T> {
} }
} }
impl<T> AsRef<Pool<T>> for Pool<T> { impl<T: Pooled + vk::Handle + Clone> Pool<T> {
fn as_ref(&self) -> &Pool<T> { pub fn get(&self) -> Result<PoolObject<T>> {
self
}
}
pub type PoolObject<T, U = Arc<Pool<T>>> = asdf::ExternallyManagedObject<T, U>;
impl<T: Pooled> Pool<T> {
pub fn get(&self) -> Result<T> {
let item = if let Some(item) = self.pool.lock().pop() { let item = if let Some(item) = self.pool.lock().pop() {
item item
} else { } else {
T::create_from_pool(self)? T::create_from_pool(self)?
}; };
Ok(item) Ok(PoolObject {
} inner: ManuallyDrop::new(item),
pool: self.clone(),
pub fn get_debug_named(&self, name: Option<impl Into<Cow<'static, str>>>) -> Result<T>
where
T: asdf::traits::DebugNameable,
{
let obj = self.get()?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ name: None,
let name = name.map(Into::into).unwrap_or_default(); })
<T as asdf::traits::DebugNameable>::debug_name(&obj, &self.device, &name);
} }
pub fn get_named(&self, name: Option<impl Into<Cow<'static, str>>>) -> Result<PoolObject<T>> {
let mut obj = self.get()?;
if let Some(name) = name {
obj.name_object(name);
}
Ok(obj) Ok(obj)
} }
} }
@ -932,233 +937,3 @@ macro_rules! define_device_owned_handle {
)? )?
}; };
} }
// This module is an experiment in a more generic way to manage device-owned resources.
// #[cfg(false)]
pub(crate) mod asdf {
use std::{
mem::{ManuallyDrop, MaybeUninit},
ops::{Deref, DerefMut},
sync::Arc,
};
use ash::vk;
use crate::{device::DeviceInner, util::DebugName};
pub mod traits {
/// A trait describing an object owned by some manager-type, which is
/// responsible for destroying it.
pub trait ExternallyManagedObject<T> {
/// # Safety
/// The caller must ensure this function is only called once for a given object.
unsafe fn destroy(self, owner: &T);
}
/// A trait describing an object which can have a debug name assigned to it.
pub trait DebugNameable {
fn debug_name(&self, device: &super::DeviceInner, name: &str);
}
}
/// Wrapper for types which are owned by another type `O`, which is responsible for destruction.
#[derive(Debug)]
pub struct ExternallyManagedObject<T: traits::ExternallyManagedObject<O>, O> {
inner: ManuallyDrop<T>,
owner: O,
}
impl<T: traits::ExternallyManagedObject<O>, O> Deref for ExternallyManagedObject<T, O> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: traits::ExternallyManagedObject<O>, O> DerefMut for ExternallyManagedObject<T, O> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T: traits::ExternallyManagedObject<O>, O> ExternallyManagedObject<T, O> {
pub fn new(inner: T, owner: O) -> Self {
Self {
inner: ManuallyDrop::new(inner),
owner,
}
}
pub fn owner(&self) -> &O {
&self.owner
}
pub fn map_inner<U>(self, f: impl FnOnce(T) -> U) -> ExternallyManagedObject<U, O>
where
U: traits::ExternallyManagedObject<O>,
{
unsafe {
let mut this = MaybeUninit::new(self);
let inner = ManuallyDrop::take(&mut this.assume_init_mut().inner);
let owner = core::ptr::read(&raw const this.assume_init_mut().owner);
let new_inner = f(inner);
ExternallyManagedObject {
inner: ManuallyDrop::new(new_inner),
owner,
}
}
}
pub fn map_owner<U>(self, f: impl FnOnce(O) -> U) -> ExternallyManagedObject<T, U>
where
T: traits::ExternallyManagedObject<U>,
{
unsafe {
let mut this = MaybeUninit::new(self);
let inner = ManuallyDrop::take(&mut this.assume_init_mut().inner);
// get the old owner without calling `Self::drop`
let owner = core::ptr::read(&raw const this.assume_init_mut().owner);
ExternallyManagedObject {
inner: ManuallyDrop::new(inner),
owner: f(owner),
}
}
}
}
impl<T, O> Drop for ExternallyManagedObject<T, O>
where
T: traits::ExternallyManagedObject<O>,
{
fn drop(&mut self) {
unsafe {
let inner = ManuallyDrop::take(&mut self.inner);
inner.destroy(&self.owner);
}
}
}
/// A wrapper for vulkan types which are owned by the device, taking care of destruction.
#[derive(Debug)]
pub struct DeviceObject<
T: traits::ExternallyManagedObject<O>,
O: AsRef<super::DeviceInner> = Arc<super::DeviceInner>,
> {
inner: ExternallyManagedObject<T, O>,
#[allow(dead_code)]
name: Option<DebugName>,
}
impl<
T: traits::ExternallyManagedObject<O> + traits::DebugNameable,
O: AsRef<super::DeviceInner>,
> DeviceObject<T, O>
{
pub fn new_debug_named(owner: O, inner: T, name: Option<impl Into<DebugName>>) -> Self {
let name = name.map(Into::into);
if let Some(ref name) = name {
traits::DebugNameable::debug_name(&inner, owner.as_ref(), name);
}
let obj = ExternallyManagedObject::new(inner, owner);
Self { inner: obj, name }
}
}
impl<T: traits::ExternallyManagedObject<O>, O: AsRef<super::DeviceInner>> DeviceObject<T, O> {
pub fn new(owner: O, inner: T) -> Self {
let inner = ExternallyManagedObject::new(inner, owner);
Self { inner, name: None }
}
pub fn device(&self) -> &O {
self.inner.owner()
}
}
impl<T, O> Deref for DeviceObject<T, O>
where
T: traits::ExternallyManagedObject<O>,
O: AsRef<super::DeviceInner>,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
mod impls {
use crate::device::{DeviceInner, DevicePools, GpuAllocation};
use super::*;
impl<T: AsRef<DeviceInner>> traits::ExternallyManagedObject<T> for ash::vk::Semaphore {
unsafe fn destroy(self, device: &T) {
unsafe {
device.as_ref().raw.destroy_semaphore(self, None);
}
}
}
impl<T: AsRef<DeviceInner>> traits::ExternallyManagedObject<T> for GpuAllocation {
unsafe fn destroy(self, device: &T) {
_ = device.as_ref().alloc2.lock().free(self);
}
}
impl traits::ExternallyManagedObject<DevicePools> for vk::Semaphore {
unsafe fn destroy(self, owner: &DevicePools) {
owner
.binary_semaphores
.push(crate::sync::BinarySemaphore(self));
}
}
impl<T> traits::DebugNameable for T
where
T: vk::Handle + Copy,
{
fn debug_name(&self, device: &super::DeviceInner, name: &str) {
unsafe {
device.debug_name_object(*self, name);
}
}
}
}
#[allow(dead_code)]
#[cfg(test)]
fn asdf() {
use crate::device::{DevicePools, GpuAllocation};
fn summon<T>() -> T {
unimplemented!()
}
let _inner_ref: DeviceObject<vk::Semaphore, &DeviceInner> = DeviceObject::new_debug_named(
summon::<&DeviceInner>(),
summon::<vk::Semaphore>(),
Some("my semaphore"),
);
let _device_owned: DeviceObject<vk::Semaphore, super::Device> =
DeviceObject::new_debug_named(
summon::<super::Device>(),
summon::<vk::Semaphore>(),
Some("my other semaphore"),
);
let _allocation: DeviceObject<GpuAllocation, Arc<super::DeviceInner>> = DeviceObject::new(
summon::<Arc<super::DeviceInner>>(),
summon::<GpuAllocation>(),
);
let _pool_owned: ExternallyManagedObject<vk::Semaphore, DevicePools> =
ExternallyManagedObject::new(summon::<vk::Semaphore>(), summon::<DevicePools>());
}
}

View file

@ -809,9 +809,9 @@ impl EguiState {
"crates/renderer/shaders/egui_vert.spv", "crates/renderer/shaders/egui_vert.spv",
)?; )?;
let pipeline = pipeline::Pipeline::new_graphics( let pipeline = pipeline::Pipeline::new(
device.clone(), device.clone(),
pipeline::GraphicsPipelineDesc { pipeline::PipelineDesc::Graphics(pipeline::GraphicsPipelineDesc {
flags: Default::default(), flags: Default::default(),
name: Some("egui-pipeline".into()), name: Some("egui-pipeline".into()),
shader_stages: &[ shader_stages: &[
@ -904,7 +904,7 @@ impl EguiState {
dynamic_states: &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR], dynamic_states: &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR],
..Default::default() ..Default::default()
}), }),
}, }),
)?; )?;
Ok(Self { Ok(Self {

View file

@ -4,7 +4,7 @@ use ash::{ext, prelude::*, vk};
use crate::{ use crate::{
define_device_owned_handle, define_device_owned_handle,
device::{Device, DeviceHandle, DeviceObject}, device::{Device, DeviceOwnedDebugObject},
make_extension, make_extension,
}; };
@ -40,6 +40,12 @@ pub struct PipelineLayoutDesc<'a> {
pub name: Option<Cow<'static, str>>, pub name: Option<Cow<'static, str>>,
} }
#[derive(Debug)]
pub enum PipelineDesc<'a> {
Compute(ComputePipelineDesc<'a>),
Graphics(GraphicsPipelineDesc<'a>),
}
#[derive(Debug)] #[derive(Debug)]
pub struct ComputePipelineDesc<'a> { pub struct ComputePipelineDesc<'a> {
pub flags: vk::PipelineCreateFlags, pub flags: vk::PipelineCreateFlags,
@ -245,17 +251,18 @@ impl DescriptorPool {
let info = &vk::DescriptorSetAllocateInfo::default() let info = &vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(self.handle()) .descriptor_pool(self.handle())
.set_layouts(&layouts); .set_layouts(&layouts);
let sets = unsafe { self.device().dev().allocate_descriptor_sets(info)? }; let sets = unsafe { self.device().dev().allocate_descriptor_sets(&info)? };
for (&set, desc) in sets.iter().zip(descs) { for (&set, desc) in sets.iter().zip(descs) {
if let Some(name) = desc.name.as_ref() { if let Some(name) = desc.name.as_ref() {
unsafe { self.device().debug_name_object(set, name) }; unsafe { self.device().debug_name_object(set, &name) };
} }
} }
Ok(sets) Ok(sets)
} }
// pub fn free(&self) {}
#[allow(dead_code)] #[allow(dead_code)]
pub fn reset(&self) -> VkResult<()> { pub fn reset(&self) -> VkResult<()> {
unsafe { unsafe {
@ -467,13 +474,18 @@ impl ShaderModule {
#[derive(Debug)] #[derive(Debug)]
pub struct Pipeline { pub struct Pipeline {
pipeline: DeviceObject<vk::Pipeline>, pipeline: DeviceOwnedDebugObject<vk::Pipeline>,
bind_point: vk::PipelineBindPoint, bind_point: vk::PipelineBindPoint,
} }
impl DeviceHandle for vk::Pipeline { impl Drop for Pipeline {
unsafe fn destroy(&mut self, device: &Device) { fn drop(&mut self) {
unsafe { device.raw.destroy_pipeline(*self, None) }; unsafe {
self.pipeline
.dev()
.dev()
.destroy_pipeline(self.pipeline.handle(), None);
}
} }
} }
@ -488,38 +500,35 @@ impl ShaderStageDesc<'_> {
} }
impl Pipeline { impl Pipeline {
pub fn new_compute(device: Device, desc: ComputePipelineDesc) -> crate::Result<Self> { pub fn new(device: Device, desc: PipelineDesc) -> VkResult<Self> {
let name: Option<Cow<'static, str>>;
let bind_point: vk::PipelineBindPoint;
let result = match desc {
PipelineDesc::Compute(desc) => {
name = desc.name;
bind_point = vk::PipelineBindPoint::COMPUTE;
let info = &vk::ComputePipelineCreateInfo::default() let info = &vk::ComputePipelineCreateInfo::default()
.flags(desc.flags) .flags(desc.flags)
.layout(desc.layout.handle()) .layout(desc.layout.handle())
.base_pipeline_handle( .base_pipeline_handle(
desc.base_pipeline desc.base_pipeline
.map(|p| p.raw()) .map(|p| p.handle())
.unwrap_or(vk::Pipeline::null()), .unwrap_or(vk::Pipeline::null()),
) )
.stage(desc.shader_stage.as_create_info()); .stage(desc.shader_stage.as_create_info());
let pipeline = unsafe { unsafe {
device device.dev().create_compute_pipelines(
.dev() vk::PipelineCache::null(),
.create_compute_pipelines(
device.pools.pipeline_cache.raw,
core::slice::from_ref(info), core::slice::from_ref(info),
None, 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(pipeline, device, desc.name),
bind_point: vk::PipelineBindPoint::COMPUTE,
})
} }
}
PipelineDesc::Graphics(desc) => {
name = desc.name;
bind_point = vk::PipelineBindPoint::GRAPHICS;
pub fn new_graphics(device: Device, desc: GraphicsPipelineDesc) -> crate::Result<Self> {
let stages = desc let stages = desc
.shader_stages .shader_stages
.iter() .iter()
@ -575,27 +584,32 @@ impl Pipeline {
}); });
let multisample = desc.multisample.map(|state| { let multisample = desc.multisample.map(|state| {
vk::PipelineMultisampleStateCreateInfo::default() let info = vk::PipelineMultisampleStateCreateInfo::default()
.flags(state.flags) .flags(state.flags)
.min_sample_shading(state.min_sample_shading) .min_sample_shading(state.min_sample_shading)
.rasterization_samples(state.rasterization_samples) .rasterization_samples(state.rasterization_samples)
.sample_mask(state.sample_mask) .sample_mask(state.sample_mask)
.sample_shading_enable(state.sample_shading_enable) .sample_shading_enable(state.sample_shading_enable)
.alpha_to_coverage_enable(state.alpha_to_coverage_enable) .alpha_to_coverage_enable(state.alpha_to_coverage_enable)
.alpha_to_one_enable(state.alpha_to_one_enable) .alpha_to_one_enable(state.alpha_to_one_enable);
info
}); });
let color_blend = desc.color_blend.map(|state| { let color_blend = desc.color_blend.map(|state| {
vk::PipelineColorBlendStateCreateInfo::default() let info = vk::PipelineColorBlendStateCreateInfo::default()
.flags(state.flags) .flags(state.flags)
.attachments(state.attachments) .attachments(state.attachments)
.blend_constants(state.blend_constants) .blend_constants(state.blend_constants)
.logic_op(state.logic_op.unwrap_or(Default::default())) .logic_op(state.logic_op.unwrap_or(Default::default()))
.logic_op_enable(state.logic_op.is_some()) .logic_op_enable(state.logic_op.is_some());
info
}); });
let depth_stencil = desc.depth_stencil.map(|state| { let depth_stencil = desc.depth_stencil.map(|state| {
let mut info = vk::PipelineDepthStencilStateCreateInfo::default().flags(state.flags); let mut info =
vk::PipelineDepthStencilStateCreateInfo::default().flags(state.flags);
if let Some(depth) = state.depth { if let Some(depth) = state.depth {
info = info info = info
@ -621,16 +635,20 @@ impl Pipeline {
}); });
let dynamic = desc.dynamic.map(|state| { let dynamic = desc.dynamic.map(|state| {
vk::PipelineDynamicStateCreateInfo::default() let info = vk::PipelineDynamicStateCreateInfo::default()
.flags(state.flags) .flags(state.flags)
.dynamic_states(state.dynamic_states) .dynamic_states(state.dynamic_states);
info
}); });
let mut rendering = desc.rendering.map(|state| { let mut rendering = desc.rendering.map(|state| {
vk::PipelineRenderingCreateInfo::default() let info = vk::PipelineRenderingCreateInfo::default()
.color_attachment_formats(state.color_formats) .color_attachment_formats(state.color_formats)
.depth_attachment_format(state.depth_format.unwrap_or_default()) .depth_attachment_format(state.depth_format.unwrap_or_default())
.stencil_attachment_format(state.stencil_format.unwrap_or_default()) .stencil_attachment_format(state.stencil_format.unwrap_or_default());
info
}); });
fn option_to_ptr<T>(option: &Option<T>) -> *const T { fn option_to_ptr<T>(option: &Option<T>) -> *const T {
@ -658,7 +676,7 @@ impl Pipeline {
subpass: desc.subpass.unwrap_or(0), subpass: desc.subpass.unwrap_or(0),
base_pipeline_handle: desc base_pipeline_handle: desc
.base_pipeline .base_pipeline
.map(|piepline| *piepline.pipeline) .map(|piepline| piepline.pipeline.handle())
.unwrap_or(vk::Pipeline::null()), .unwrap_or(vk::Pipeline::null()),
..Default::default() ..Default::default()
}; };
@ -667,143 +685,39 @@ impl Pipeline {
info = info.push_next(rendering) info = info.push_next(rendering)
} }
let pipeline = unsafe { unsafe {
device device.dev().create_graphics_pipelines(
.dev() vk::PipelineCache::null(),
.create_graphics_pipelines(
device.pools.pipeline_cache.raw,
core::slice::from_ref(&info), core::slice::from_ref(&info),
None, 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]
let pipeline = match result {
Ok(pipelines) => pipelines[0],
Err((pipelines, error)) => {
tracing::error!("failed to create pipelines with :{error}");
for pipeline in pipelines {
unsafe {
device.dev().destroy_pipeline(pipeline, None);
}
}
return Err(error.into());
}
}; };
Ok(Self { Ok(Self {
pipeline: DeviceObject::new(pipeline, device, desc.name), pipeline: DeviceOwnedDebugObject::new(device, pipeline, name)?,
bind_point: vk::PipelineBindPoint::GRAPHICS, bind_point,
}) })
} }
pub fn raw(&self) -> vk::Pipeline { pub fn handle(&self) -> vk::Pipeline {
*self.pipeline self.pipeline.handle()
} }
pub fn bind_point(&self) -> vk::PipelineBindPoint { pub fn bind_point(&self) -> vk::PipelineBindPoint {
self.bind_point 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<Arc<DeviceInner>> for PipelineCache {
unsafe fn destroy(self, owner: &Arc<DeviceInner>) {
if let Ok(data) = self.export(&owner.raw) {
_ = 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<u8>)> {
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<Self> {
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<Vec<u8>> {
let data = unsafe { device.get_pipeline_cache_data(self.raw)? };
Ok(data)
}
}
}

View file

@ -201,9 +201,9 @@ impl Wireframe {
"crates/renderer/shaders/wireframe.spv", "crates/renderer/shaders/wireframe.spv",
)?; )?;
let pipeline = pipeline::Pipeline::new_graphics( let pipeline = pipeline::Pipeline::new(
device.clone(), device.clone(),
pipeline::GraphicsPipelineDesc { pipeline::PipelineDesc::Graphics(pipeline::GraphicsPipelineDesc {
flags: Default::default(), flags: Default::default(),
name: Some("wireframe-pipeline".into()), name: Some("wireframe-pipeline".into()),
shader_stages: &[ shader_stages: &[
@ -298,7 +298,7 @@ impl Wireframe {
dynamic_states: &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR], dynamic_states: &[vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR],
..Default::default() ..Default::default()
}), }),
}, }),
)?; )?;
Ok((pipeline, pipeline_layout)) Ok((pipeline, pipeline_layout))

View file

@ -1,3 +1,5 @@
#[cfg(debug_assertions)]
use std::borrow::Cow;
use std::{ use std::{
future::Future, future::Future,
marker::PhantomData, marker::PhantomData,
@ -5,10 +7,7 @@ use std::{
time::Duration, time::Duration,
}; };
use crate::device::{ use crate::device::{DeviceObject, Pool, PoolObject, Pooled};
DevicePools, Pool, PoolObject, Pooled,
asdf::{DeviceObject, traits::ExternallyManagedObject as ExternallyManagedObjectTrait},
};
use crate::{Result, device::DeviceInner}; use crate::{Result, device::DeviceInner};
use super::Device; use super::Device;
@ -156,12 +155,8 @@ impl SyncThreadpool {
} }
pub enum Fence { pub enum Fence {
Dedicated { Dedicated { fence: DeviceObject<vk::Fence> },
fence: DeviceObject<vk::Fence>, Pooled { fence: PoolObject<vk::Fence> },
},
Pooled {
fence: PoolObject<vk::Fence, Arc<Pool<vk::Fence>>>,
},
} }
impl Pooled for vk::Fence { impl Pooled for vk::Fence {
@ -176,20 +171,6 @@ impl Pooled for vk::Fence {
} }
} }
impl ExternallyManagedObjectTrait<Arc<Pool<vk::Fence>>> for vk::Fence {
unsafe fn destroy(self, pool: &Arc<Pool<vk::Fence>>) {
pool.push(self);
}
}
impl ExternallyManagedObjectTrait<Arc<DeviceInner>> for vk::Fence {
unsafe fn destroy(self, device: &Arc<DeviceInner>) {
unsafe {
device.raw.destroy_fence(self, None);
}
}
}
impl std::fmt::Debug for Fence { impl std::fmt::Debug for Fence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Fence").field("fence", &self.raw()).finish() f.debug_struct("Fence").field("fence", &self.raw()).finish()
@ -204,15 +185,16 @@ impl Fence {
.create_fence(&vk::FenceCreateInfo::default(), None)? .create_fence(&vk::FenceCreateInfo::default(), None)?
}; };
Ok(Self::Dedicated { Ok(Self::Dedicated {
fence: DeviceObject::new_debug_named(device.shared, fence, name), fence: DeviceObject::new(fence, device, name.map(Into::into)),
}) })
} }
pub fn from_pool(pool: &Arc<Pool<vk::Fence>>, name: Option<&'static str>) -> Result<Fence> { pub fn from_pool(pool: &Pool<vk::Fence>, name: Option<&'static str>) -> Result<Fence> {
let fence = pool.get_debug_named(name)?; let mut fence = pool.get()?;
#[cfg(debug_assertions)]
Ok(Self::Pooled { if let Some(name) = name {
fence: PoolObject::new(fence, pool.clone()), fence.name_object(name);
}) }
Ok(Self::Pooled { fence })
} }
pub fn raw(&self) -> vk::Fence { pub fn raw(&self) -> vk::Fence {
@ -224,8 +206,8 @@ impl Fence {
fn device(&self) -> &Arc<DeviceInner> { fn device(&self) -> &Arc<DeviceInner> {
match self { match self {
Fence::Dedicated { fence } => &fence.device(), Fence::Dedicated { fence } => &fence.device().shared,
Fence::Pooled { fence } => &fence.owner().device, Fence::Pooled { fence } => fence.device(),
} }
} }
@ -269,82 +251,24 @@ pub enum SemaphoreType {
Timeline(u64), Timeline(u64),
} }
#[derive(Debug, Clone, Copy)]
pub enum SemaphoreInner {
Binary(vk::Semaphore),
Timeline(vk::Semaphore),
}
impl ExternallyManagedObjectTrait<Arc<DevicePools>> for SemaphoreInner {
unsafe fn destroy(self, owner: &Arc<DevicePools>) {
match self {
SemaphoreInner::Binary(semaphore) => {
owner.binary_semaphores.push(BinarySemaphore(semaphore))
}
SemaphoreInner::Timeline(semaphore) => {
owner.timeline_semaphores.push(TimelineSemaphore(semaphore))
}
}
}
}
impl ExternallyManagedObjectTrait<Arc<DeviceInner>> for SemaphoreInner {
unsafe fn destroy(self, owner: &Arc<DeviceInner>) {
match self {
SemaphoreInner::Binary(semaphore) | SemaphoreInner::Timeline(semaphore) => {
unsafe { owner.raw.destroy_semaphore(semaphore, None) };
}
}
}
}
impl crate::device::asdf::traits::DebugNameable for SemaphoreInner {
fn debug_name(&self, device: &DeviceInner, name: &str) {
unsafe {
device.debug_name_object(self.raw(), name);
}
}
}
impl SemaphoreInner {
pub fn raw(&self) -> vk::Semaphore {
match self {
SemaphoreInner::Binary(semaphore) | SemaphoreInner::Timeline(semaphore) => *semaphore,
}
}
pub fn semaphore_type(&self) -> SemaphoreType {
match self {
SemaphoreInner::Binary(_) => SemaphoreType::Binary,
SemaphoreInner::Timeline(_) => SemaphoreType::Timeline(!0),
}
}
}
impl From<BinarySemaphore> for SemaphoreInner {
fn from(value: BinarySemaphore) -> Self {
SemaphoreInner::Binary(value.0)
}
}
impl From<TimelineSemaphore> for SemaphoreInner {
fn from(value: TimelineSemaphore) -> Self {
SemaphoreInner::Timeline(value.0)
}
}
pub enum Semaphore { pub enum Semaphore {
Dedicated { Dedicated {
semaphore: DeviceObject<SemaphoreInner>, semaphore_type: SemaphoreType,
semaphore: DeviceObject<vk::Semaphore>,
}, },
Pooled { Pooled {
#[allow(private_interfaces)] semaphore_type: SemaphoreType,
semaphore: PoolObject<SemaphoreInner, Arc<DevicePools>>, semaphore: vk::Semaphore,
device: Device,
#[cfg(debug_assertions)]
name: Option<Cow<'static, str>>,
}, },
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub(crate) struct BinarySemaphore(pub(crate) vk::Semaphore); pub(crate) struct BinarySemaphore(vk::Semaphore);
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub(crate) struct TimelineSemaphore(pub(crate) vk::Semaphore); pub(crate) struct TimelineSemaphore(vk::Semaphore);
// This is just so that ash can name these semaphore newtypes // This is just so that ash can name these semaphore newtypes
impl vk::Handle for BinarySemaphore { impl vk::Handle for BinarySemaphore {
@ -392,6 +316,36 @@ impl Pooled for TimelineSemaphore {
} }
} }
impl Drop for Semaphore {
fn drop(&mut self) {
if let Semaphore::Pooled {
device,
semaphore_type,
semaphore,
name,
} = self
{
#[cfg(debug_assertions)]
if name.is_some() {
// reset the name to avoid confusion in case this semaphore is re-used
unsafe { device.debug_name_object(*semaphore, "") };
}
match semaphore_type {
SemaphoreType::Binary => device
.pools
.binary_semaphores
.push(BinarySemaphore(*semaphore)),
SemaphoreType::Timeline(_) => {
device
.pools
.timeline_semaphores
.push(TimelineSemaphore(*semaphore));
}
}
}
}
}
impl Semaphore { impl Semaphore {
pub fn new_dedicated( pub fn new_dedicated(
device: Device, device: Device,
@ -412,13 +366,9 @@ impl Semaphore {
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info); let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
let inner = unsafe { device.dev().create_semaphore(&create_info, None)? }; let inner = unsafe { device.dev().create_semaphore(&create_info, None)? };
let inner = match semaphore_type {
SemaphoreType::Binary => SemaphoreInner::Binary(inner),
SemaphoreType::Timeline(_) => SemaphoreInner::Timeline(inner),
};
Ok(Self::Dedicated { Ok(Self::Dedicated {
semaphore: DeviceObject::new_debug_named(device.shared, inner, name), semaphore_type,
semaphore: DeviceObject::new(inner, device, name.map(Into::into)),
}) })
} }
@ -429,35 +379,48 @@ impl Semaphore {
) -> Result<Self> { ) -> Result<Self> {
let semaphore = match semaphore_type { let semaphore = match semaphore_type {
SemaphoreType::Binary => { SemaphoreType::Binary => {
let semaphore: SemaphoreInner = if let Some(semaphore) = device.pools.binary_semaphores.pop() {
device.pools.binary_semaphores.get_debug_named(name)?.into(); semaphore.0
PoolObject::new(semaphore, device.pools) } else {
let mut type_info = vk::SemaphoreTypeCreateInfo::default()
.semaphore_type(vk::SemaphoreType::BINARY);
let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
unsafe { device.raw.create_semaphore(&create_info, None)? }
}
} }
SemaphoreType::Timeline(value) => { SemaphoreType::Timeline(value) => {
let semaphore: SemaphoreInner = device if let Some(semaphore) = device.pools.binary_semaphores.pop() {
.pools semaphore.0
.timeline_semaphores } else {
.get_debug_named(name)? let mut type_info = vk::SemaphoreTypeCreateInfo::default()
.into(); .semaphore_type(vk::SemaphoreType::TIMELINE)
.initial_value(value);
let info = vk::SemaphoreSignalInfo::default() let create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
.semaphore(semaphore.raw()) unsafe { device.raw.create_semaphore(&create_info, None)? }
.value(value);
unsafe {
device.raw.signal_semaphore(&info)?;
} }
PoolObject::new(semaphore, device.pools)
} }
}; };
Ok(Self::Pooled { semaphore }) #[cfg(debug_assertions)]
if let Some(name) = name {
unsafe {
device.debug_name_object(semaphore, name);
}
}
Ok(Self::Pooled {
semaphore_type,
semaphore,
device,
#[cfg(debug_assertions)]
name: name.map(Into::into),
})
} }
pub fn semaphore(&self) -> vk::Semaphore { pub fn semaphore(&self) -> vk::Semaphore {
match self { match self {
Semaphore::Dedicated { semaphore, .. } => semaphore.raw(), Semaphore::Dedicated { semaphore, .. } => **semaphore,
Semaphore::Pooled { semaphore, .. } => semaphore.raw(), Semaphore::Pooled { semaphore, .. } => *semaphore,
} }
} }
} }