use super::super::{
BufferHandle, ComputePipelineHandle, DeviceHandle, PipelineHandle, RenderTargetHandle, SamplerHandle, ShaderHandle,
SurfaceHandle, TextureHandle,
};
use crate::timeline::TimelineValue;
use crate::types::{DepthFormat, TextureFormat};
use ash::vk;
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicI32, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
pub const MAX_BINDLESS_RESOURCES: u32 = 16384;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum SlotKey {
StorageBuffer(u32),
UniformBuffer(u32),
SampledTexture(u32),
StorageImage(u32),
Sampler(u32),
}
pub(crate) struct PendingSlotReclamation {
pub slot: SlotKey,
pub requirements: Vec<(super::ContextHandle, u64)>,
}
fn slot_requirements_met(
requirements: &[(super::ContextHandle, u64)],
completed_values: &HashMap<super::ContextHandle, u64>,
) -> bool {
requirements
.iter()
.all(|(ctx_id, required_seq)| completed_values.get(ctx_id).is_none_or(|&v| v >= *required_seq))
}
pub(crate) struct DescriptorRegistry {
pub resource_registry: ResourceRegistry,
pub slot_last_seen: HashMap<SlotKey, HashMap<super::ContextHandle, u64>>,
pub pending_slot_reclamations: Vec<PendingSlotReclamation>,
retained_users: HashMap<SlotKey, u32>,
}
impl DescriptorRegistry {
pub(crate) fn new() -> Self {
Self {
resource_registry: ResourceRegistry::new(),
slot_last_seen: HashMap::new(),
pending_slot_reclamations: Vec::new(),
retained_users: HashMap::new(),
}
}
pub(crate) fn pin_retained_slots(&mut self, slots: impl IntoIterator<Item = SlotKey>) {
for slot in slots {
*self.retained_users.entry(slot).or_insert(0) += 1;
}
}
pub(crate) fn unpin_retained_slots(&mut self, slots: impl IntoIterator<Item = SlotKey>) {
for slot in slots {
if let Some(count) = self.retained_users.get_mut(&slot) {
*count = count.saturating_sub(1);
if *count == 0 {
self.retained_users.remove(&slot);
}
}
}
}
#[cfg(test)]
pub(crate) fn retained_user_count(&self, slot: SlotKey) -> u32 {
self.retained_users.get(&slot).copied().unwrap_or(0)
}
pub(crate) fn retained_pins_clear(&self, slots: &[SlotKey]) -> bool {
slots
.iter()
.all(|slot| self.retained_users.get(slot).copied().unwrap_or(0) == 0)
}
pub(crate) fn record_slot_usage(
&mut self,
ctx: super::ContextHandle,
seq: u64,
slots: impl IntoIterator<Item = SlotKey>,
) {
for slot in slots {
self.slot_last_seen
.entry(slot)
.or_default()
.entry(ctx)
.and_modify(|v| *v = (*v).max(seq))
.or_insert(seq);
}
}
pub(crate) fn queue_slot_reclamation(&mut self, slot: SlotKey) {
let requirements: Vec<_> = self
.slot_last_seen
.remove(&slot)
.map(|m| m.into_iter().collect())
.unwrap_or_default();
self.pending_slot_reclamations
.push(PendingSlotReclamation { slot, requirements });
}
pub(crate) fn reclaim_buffer_slots(&mut self, handle: BufferHandle) -> Vec<SlotKey> {
let slots = self.resource_registry.extract_buffer_slots(handle);
for slot in slots.iter().copied() {
self.queue_slot_reclamation(slot);
}
slots
}
pub(crate) fn buffer_slot_keys(&self, handle: BufferHandle) -> Vec<SlotKey> {
self.resource_registry.buffer_slot_keys(handle)
}
pub(crate) fn texture_slot_keys(&self, handle: TextureHandle) -> Vec<SlotKey> {
self.resource_registry.texture_slot_keys(handle)
}
pub(crate) fn reclaim_texture_slots(&mut self, handle: TextureHandle) {
let slots = self.resource_registry.extract_texture_slots(handle);
for slot in slots {
self.queue_slot_reclamation(slot);
}
}
pub(crate) fn reclaim_sampler_slots(&mut self, handle: SamplerHandle) {
let slots = self.resource_registry.extract_sampler_slots(handle);
for slot in slots {
self.queue_slot_reclamation(slot);
}
}
pub(crate) fn drain_ready_slot_reclamations(&mut self, completed_values: &HashMap<super::ContextHandle, u64>) {
let mut i = 0;
while i < self.pending_slot_reclamations.len() {
let slot = self.pending_slot_reclamations[i].slot;
let gpu_ready = self.pending_slot_reclamations[i]
.requirements
.iter()
.all(|(ctx_id, required_seq)| completed_values.get(ctx_id).is_none_or(|&v| v >= *required_seq));
let pin_clear = self.retained_users.get(&slot).copied().unwrap_or(0) == 0;
if gpu_ready && pin_clear {
let entry = self.pending_slot_reclamations.swap_remove(i);
self.resource_registry.free_slot(entry.slot);
} else {
i += 1;
}
}
}
pub(crate) fn bindless_retirement_requirements_for_buffer(
&self,
handle: BufferHandle,
base: Vec<(super::ContextHandle, u64)>,
) -> Vec<(super::ContextHandle, u64)> {
let slots = self.resource_registry.buffer_slot_keys(handle);
self.merge_slot_requirements(&slots, base)
}
pub(crate) fn bindless_retirement_requirements_for_texture(
&self,
handle: TextureHandle,
base: Vec<(super::ContextHandle, u64)>,
) -> Vec<(super::ContextHandle, u64)> {
let slots = self.resource_registry.texture_slot_keys(handle);
self.merge_slot_requirements(&slots, base)
}
fn merge_slot_requirements(
&self,
slots: &[SlotKey],
base: Vec<(super::ContextHandle, u64)>,
) -> Vec<(super::ContextHandle, u64)> {
let mut merged: HashMap<super::ContextHandle, u64> = base.into_iter().collect();
for &slot in slots {
if let Some(map) = self.slot_last_seen.get(&slot) {
for (ctx, seq) in map.iter() {
merged.entry(*ctx).and_modify(|v| *v = (*v).max(*seq)).or_insert(*seq);
}
}
}
merged.into_iter().collect()
}
}
pub(super) fn snapshot_context_completed_values(
device: &ash::Device,
contexts: &SharedContextMap,
for_device: super::DeviceHandle,
) -> HashMap<super::ContextHandle, u64> {
contexts
.read()
.unwrap()
.iter()
.filter_map(|(&id, sc_arc)| {
let sc = sc_arc.lock().unwrap();
if sc.device != for_device {
return None;
}
let v = unsafe { device.get_semaphore_counter_value(sc.timeline_semaphore).unwrap_or(0) };
Some((id, v))
})
.collect()
}
pub mod bindless_bindings {
pub const SCATTERED: u32 = 0;
pub const BROADCAST: u32 = 1;
pub const INTERPOLATED: u32 = 2;
pub const DIRECT_SPATIAL: u32 = 3;
pub const FILTER_CONFIG: u32 = 4;
pub const STORAGE_BUFFERS: u32 = SCATTERED;
pub const UNIFORM_BUFFERS: u32 = BROADCAST;
pub const SAMPLED_IMAGES: u32 = INTERPOLATED;
pub const STORAGE_IMAGES: u32 = DIRECT_SPATIAL;
pub const SAMPLERS: u32 = FILTER_CONFIG;
}
pub use super::super::shared::{PushLayout, TOTAL_PUSH_BYTES};
use super::super::shared::SlotAllocator;
#[derive(Default)]
pub(crate) struct ResourceRegistry {
storage_buffer: SlotAllocator,
uniform_buffer: SlotAllocator,
sampled_texture: SlotAllocator,
storage_image: SlotAllocator,
sampler: SlotAllocator,
pub buffer_indices: HashMap<BufferHandle, (u32, bool)>,
pub texture_indices: HashMap<TextureHandle, (u32, bool)>,
pub sampler_indices: HashMap<SamplerHandle, u32>,
}
impl ResourceRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register_buffer(&mut self, handle: BufferHandle, is_storage: bool) -> u32 {
let index = if is_storage {
self.storage_buffer.alloc()
} else {
self.uniform_buffer.alloc()
};
self.buffer_indices.insert(handle, (index, is_storage));
index
}
pub fn ensure_storage_start(&mut self, min: u32) {
self.storage_buffer.ensure_minimum_next(min);
}
pub fn register_texture(&mut self, handle: TextureHandle, is_storage_image: bool) -> u32 {
let index = if is_storage_image {
self.storage_image.alloc()
} else {
self.sampled_texture.alloc()
};
self.texture_indices.insert(handle, (index, is_storage_image));
index
}
pub fn register_sampler(&mut self, handle: SamplerHandle) -> u32 {
let index = self.sampler.alloc();
self.sampler_indices.insert(handle, index);
index
}
pub fn buffer_slot_keys(&self, handle: BufferHandle) -> Vec<SlotKey> {
self.buffer_indices
.get(&handle)
.map(|&(index, is_storage)| {
vec![if is_storage {
SlotKey::StorageBuffer(index)
} else {
SlotKey::UniformBuffer(index)
}]
})
.unwrap_or_default()
}
pub fn texture_slot_keys(&self, handle: TextureHandle) -> Vec<SlotKey> {
self.texture_indices
.get(&handle)
.map(|&(index, is_storage_image)| {
vec![if is_storage_image {
SlotKey::StorageImage(index)
} else {
SlotKey::SampledTexture(index)
}]
})
.unwrap_or_default()
}
pub fn extract_buffer_slots(&mut self, handle: BufferHandle) -> Vec<SlotKey> {
let mut slots = Vec::new();
if let Some((index, is_storage)) = self.buffer_indices.remove(&handle) {
slots.push(if is_storage {
SlotKey::StorageBuffer(index)
} else {
SlotKey::UniformBuffer(index)
});
}
slots
}
pub fn extract_texture_slots(&mut self, handle: TextureHandle) -> Vec<SlotKey> {
let mut slots = Vec::new();
if let Some((index, is_storage_image)) = self.texture_indices.remove(&handle) {
slots.push(if is_storage_image {
SlotKey::StorageImage(index)
} else {
SlotKey::SampledTexture(index)
});
}
slots
}
pub fn free_slot(&mut self, key: SlotKey) {
match key {
SlotKey::StorageBuffer(i) => self.storage_buffer.free(i),
SlotKey::UniformBuffer(i) => self.uniform_buffer.free(i),
SlotKey::SampledTexture(i) => self.sampled_texture.free(i),
SlotKey::StorageImage(i) => self.storage_image.free(i),
SlotKey::Sampler(i) => self.sampler.free(i),
}
}
pub fn extract_sampler_slots(&mut self, handle: SamplerHandle) -> Vec<SlotKey> {
if let Some(index) = self.sampler_indices.remove(&handle) {
vec![SlotKey::Sampler(index)]
} else {
Vec::new()
}
}
#[cfg(test)]
pub fn unregister_sampler(&mut self, handle: SamplerHandle) {
if let Some(index) = self.sampler_indices.remove(&handle) {
self.sampler.free(index);
}
}
pub fn available_slots(&self, category: crate::types::ResourceCategory) -> u32 {
let allocator = match category {
crate::types::ResourceCategory::Scattered => &self.storage_buffer,
crate::types::ResourceCategory::Broadcast => &self.uniform_buffer,
crate::types::ResourceCategory::Texture => &self.sampled_texture,
crate::types::ResourceCategory::StorageImage => &self.storage_image,
crate::types::ResourceCategory::Sampler => &self.sampler,
};
MAX_BINDLESS_RESOURCES.saturating_sub(allocator.live_count())
}
}
#[cfg(test)]
mod registry_tests {
use super::*;
fn free_buffer_slots(reg: &mut ResourceRegistry, handle: BufferHandle) {
for key in reg.extract_buffer_slots(handle) {
reg.free_slot(key);
}
}
fn free_texture_slots(reg: &mut ResourceRegistry, handle: TextureHandle) {
for key in reg.extract_texture_slots(handle) {
reg.free_slot(key);
}
}
#[test]
fn storage_buffer_slots_recycled_under_churn() {
let mut reg = ResourceRegistry::new();
for i in 0..50_000u64 {
let handle = i as BufferHandle;
reg.register_buffer(handle, true);
free_buffer_slots(&mut reg, handle);
}
assert_eq!(
reg.storage_buffer.next_fresh(),
1,
"storage buffer counter grew; slot recycling not working"
);
assert_eq!(reg.storage_buffer.free_count(), 1);
}
#[test]
fn uniform_buffer_slots_recycled_under_churn() {
let mut reg = ResourceRegistry::new();
for i in 0..50_000u64 {
let handle = i as BufferHandle;
reg.register_buffer(handle, false);
free_buffer_slots(&mut reg, handle);
}
assert_eq!(
reg.uniform_buffer.next_fresh(),
1,
"uniform buffer counter grew; slot recycling not working"
);
}
#[test]
fn sampled_texture_slots_recycled_under_churn() {
let mut reg = ResourceRegistry::new();
for i in 0..50_000u64 {
let handle = i as TextureHandle;
reg.register_texture(handle, false);
free_texture_slots(&mut reg, handle);
}
assert_eq!(reg.sampled_texture.next_fresh(), 1);
assert_eq!(reg.sampled_texture.free_count(), 1);
}
#[test]
fn storage_image_slots_recycled_under_churn() {
let mut reg = ResourceRegistry::new();
for i in 0..50_000u64 {
let handle = i as TextureHandle;
reg.register_texture(handle, true);
free_texture_slots(&mut reg, handle);
}
assert_eq!(reg.storage_image.next_fresh(), 1);
assert_eq!(reg.storage_image.free_count(), 1);
}
#[test]
fn sampler_slots_recycled_under_churn() {
let mut reg = ResourceRegistry::new();
for i in 0..5_000u64 {
let handle = i as SamplerHandle;
reg.register_sampler(handle);
reg.unregister_sampler(handle);
}
assert_eq!(reg.sampler.next_fresh(), 1);
assert_eq!(reg.sampler.free_count(), 1);
}
#[test]
fn live_resources_get_distinct_indices() {
let mut reg = ResourceRegistry::new();
const N: u64 = 64;
let mut indices: Vec<u32> = (0..N).map(|i| reg.register_buffer(i as BufferHandle, true)).collect();
indices.sort_unstable();
indices.dedup();
assert_eq!(
indices.len(),
N as usize,
"duplicate indices assigned to live resources"
);
}
#[test]
fn high_water_mark_bounded_by_live_count() {
let mut reg = ResourceRegistry::new();
const LIVE: u64 = 8;
const ROUNDS: u64 = 10_000;
for i in 0..LIVE {
reg.register_buffer(i as BufferHandle, true);
}
for i in LIVE..LIVE + ROUNDS {
free_buffer_slots(&mut reg, (i - LIVE) as BufferHandle);
reg.register_buffer(i as BufferHandle, true);
}
assert!(
reg.storage_buffer.next_fresh() <= LIVE as u32,
"counter ({}) exceeded live count ({LIVE}); slot recycling broken",
reg.storage_buffer.next_fresh()
);
}
#[test]
fn slot_deferred_until_context_retires() {
use crate::backend::ContextHandle;
let mut reg = ResourceRegistry::new();
let handle = 1u64 as BufferHandle;
let slot = reg.register_buffer(handle, true);
const CTX_A: ContextHandle = 10;
const SEQ: u64 = 5;
let slots = reg.extract_buffer_slots(handle);
assert_eq!(slots, vec![SlotKey::StorageBuffer(slot)]);
let mut pending = vec![PendingSlotReclamation {
slot: SlotKey::StorageBuffer(slot),
requirements: vec![(CTX_A, SEQ)],
}];
assert_eq!(reg.storage_buffer.free_count(), 0);
let mut retired = HashMap::from([(CTX_A, 4u64)]);
let mut i = 0;
while i < pending.len() {
let ready = pending[i]
.requirements
.iter()
.all(|(ctx, seq)| retired.get(ctx).copied().unwrap_or(0) >= *seq);
if ready {
let entry = pending.swap_remove(i);
reg.free_slot(entry.slot);
} else {
i += 1;
}
}
assert_eq!(reg.storage_buffer.free_count(), 0);
retired.insert(CTX_A, SEQ);
i = 0;
while i < pending.len() {
let ready = pending[i]
.requirements
.iter()
.all(|(ctx, seq)| retired.get(ctx).copied().unwrap_or(0) >= *seq);
if ready {
let entry = pending.swap_remove(i);
reg.free_slot(entry.slot);
} else {
i += 1;
}
}
assert_eq!(reg.storage_buffer.free_count(), 1);
}
#[test]
fn slot_waits_for_all_referencing_contexts() {
use crate::backend::ContextHandle;
let mut reg = ResourceRegistry::new();
let handle = 2u64 as BufferHandle;
let slot = reg.register_buffer(handle, true);
const CTX_A: ContextHandle = 1;
const CTX_B: ContextHandle = 2;
let mut pending = vec![PendingSlotReclamation {
slot: SlotKey::StorageBuffer(slot),
requirements: vec![(CTX_A, 3), (CTX_B, 7)],
}];
reg.extract_buffer_slots(handle);
let mut retired = HashMap::from([(CTX_A, 0u64), (CTX_B, 0u64)]);
let drain_pending = |retired: &HashMap<ContextHandle, u64>,
reg: &mut ResourceRegistry,
pending: &mut Vec<PendingSlotReclamation>| {
let mut i = 0;
while i < pending.len() {
let ready = pending[i]
.requirements
.iter()
.all(|(ctx, seq)| retired.get(ctx).copied().unwrap_or(0) >= *seq);
if ready {
let entry = pending.swap_remove(i);
reg.free_slot(entry.slot);
} else {
i += 1;
}
}
};
drain_pending(&retired, &mut reg, &mut pending);
assert_eq!(reg.storage_buffer.free_count(), 0);
retired.insert(CTX_A, 3);
drain_pending(&retired, &mut reg, &mut pending);
assert_eq!(reg.storage_buffer.free_count(), 0);
retired.insert(CTX_B, 7);
drain_pending(&retired, &mut reg, &mut pending);
assert_eq!(reg.storage_buffer.free_count(), 1);
}
#[test]
fn retained_pin_blocks_slot_free_until_unpin() {
use crate::backend::ContextHandle;
let mut dr = DescriptorRegistry::new();
let handle = 3u64 as BufferHandle;
let slot = dr.resource_registry.register_buffer(handle, true);
let slot_key = SlotKey::StorageBuffer(slot);
dr.pin_retained_slots([slot_key]);
dr.queue_slot_reclamation(slot_key);
dr.resource_registry.extract_buffer_slots(handle);
let completed: HashMap<ContextHandle, u64> = HashMap::new();
dr.drain_ready_slot_reclamations(&completed);
assert_eq!(dr.resource_registry.storage_buffer.free_count(), 0, "pin blocks free");
assert_eq!(dr.retained_user_count(slot_key), 1);
dr.unpin_retained_slots([slot_key]);
dr.drain_ready_slot_reclamations(&completed);
assert_eq!(
dr.resource_registry.storage_buffer.free_count(),
1,
"unpin then drain frees"
);
}
#[test]
fn retained_pin_shared_slot_needs_two_unpins() {
use crate::backend::ContextHandle;
let mut dr = DescriptorRegistry::new();
let handle = 4u64 as BufferHandle;
let slot = dr.resource_registry.register_buffer(handle, true);
let slot_key = SlotKey::StorageBuffer(slot);
dr.pin_retained_slots([slot_key]);
dr.pin_retained_slots([slot_key]);
dr.queue_slot_reclamation(slot_key);
dr.resource_registry.extract_buffer_slots(handle);
let completed: HashMap<ContextHandle, u64> = HashMap::new();
dr.unpin_retained_slots([slot_key]);
dr.drain_ready_slot_reclamations(&completed);
assert_eq!(dr.resource_registry.storage_buffer.free_count(), 0, "one pin remains");
dr.unpin_retained_slots([slot_key]);
dr.drain_ready_slot_reclamations(&completed);
assert_eq!(dr.resource_registry.storage_buffer.free_count(), 1);
}
#[test]
fn retained_pin_replace_frees_old_only_slots() {
use crate::backend::ContextHandle;
let mut dr = DescriptorRegistry::new();
let slot_old = dr.resource_registry.register_buffer(5, true);
let slot_new = dr.resource_registry.register_buffer(6, true);
let old_key = SlotKey::StorageBuffer(slot_old);
let new_key = SlotKey::StorageBuffer(slot_new);
dr.pin_retained_slots([old_key]);
dr.pin_retained_slots([new_key]);
dr.unpin_retained_slots([old_key]);
dr.queue_slot_reclamation(old_key);
dr.queue_slot_reclamation(new_key);
dr.resource_registry.buffer_indices.remove(&5);
dr.resource_registry.buffer_indices.remove(&6);
let completed: HashMap<ContextHandle, u64> = HashMap::new();
dr.drain_ready_slot_reclamations(&completed);
assert_eq!(dr.resource_registry.storage_buffer.free_count(), 1, "old slot freed");
assert_eq!(dr.retained_user_count(new_key), 1);
assert_eq!(dr.retained_user_count(old_key), 0);
}
}
pub(crate) struct PhysicalDeviceInfo {
pub handle: vk::PhysicalDevice,
pub properties: vk::PhysicalDeviceProperties,
pub adapter_id: u32,
pub supports_sparse_buffer: bool,
pub vk_timestamp_compute_and_graphics: bool,
pub vk_timestamp_period_ns: f32,
}
pub(crate) const MAX_CONTEXT_COMPUTE_QUEUES: u32 = 8;
pub(crate) struct SubmissionContext {
pub device: super::DeviceHandle,
pub is_device_owner: bool,
pub queue: vk::Queue,
#[allow(dead_code)]
pub queue_family: u32,
pub queue_index: Option<usize>,
pub queue_lock: std::sync::Arc<std::sync::Mutex<()>>,
pub timeline_semaphore: vk::Semaphore,
pub last_submitted_seq: u64,
pub signal_queue: std::sync::Arc<crate::signal::SignalQueue>,
pub fence_shutdown: std::sync::Arc<std::sync::atomic::AtomicBool>,
pub fence_thread: Option<std::thread::JoinHandle<()>>,
pub command_pool: vk::CommandPool,
pub free_cmd_buffers: Vec<vk::CommandBuffer>,
pub retained_compute_cbs: HashMap<u64, RetainedVkCb>,
pub timeline_cmd_buffers: std::collections::HashMap<u64, Vec<vk::CommandBuffer>>,
pub graphics_timeline_cmd_buffers: std::collections::HashMap<u64, Vec<vk::CommandBuffer>>,
pub staging_belt: super::staging::StagingBelt,
pub texture_staging_pool: super::staging::TextureStagingPool,
pub deletion_queue: DeletionQueue,
pub frame_table: SharedContextFrameTable,
pub pending_gpu_profiles: Vec<(u64, super::pending_submit::VulkanGpuProfileWork)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TimelineWaitTarget {
Context(super::ContextHandle),
DeviceOwner,
}
pub(crate) struct LogicalDevice {
pub device: ash::Device,
pub physical_device: vk::PhysicalDevice,
#[allow(dead_code)]
pub adapter_id: u32,
pub queue: vk::Queue,
pub queue_family: u32,
pub compute_queue_family: u32,
pub compute_queues: Vec<vk::Queue>,
pub compute_queues_alias_graphics: bool,
pub free_compute_queue_indices: std::sync::Mutex<std::collections::VecDeque<usize>>,
pub free_device_cmd_buffers: std::sync::Mutex<Vec<vk::CommandBuffer>>,
pub sparse_binding_queue: vk::Queue,
pub command_pool: vk::CommandPool,
pub supports_sparse_buffer: bool,
pub sparse_buffer_block_size: u64,
#[allow(dead_code)] pub sparse_memory_type_index: u32,
pub sparse_page_pool: Mutex<Option<super::sparse::SparsePagePool>>,
pub map_memory2: ash::khr::map_memory2::Device,
pub bindless_descriptor_pool: Option<vk::DescriptorPool>,
pub bindless_descriptor_set_layout: Option<vk::DescriptorSetLayout>,
pub bindless_descriptor_set: Option<vk::DescriptorSet>,
pub bindless_pipeline_layout: Option<vk::PipelineLayout>,
pub descriptors: Arc<Mutex<DescriptorRegistry>>,
pub deletion_queue: Mutex<DeviceDeletionQueue>,
pub pending_buffer_gpu_releases: Mutex<Vec<PendingBufferGpuRelease>>,
pub timeline_next: Arc<AtomicU64>,
pub retired_floor: AtomicU64,
pub timeline_wait_targets: Mutex<BTreeMap<u64, TimelineWaitTarget>>,
pub timeline_retired: AtomicU64,
pub pipeline_cache: vk::PipelineCache,
pub queue_lock: Arc<Mutex<()>>,
pub active_context_queue_locks: Mutex<Vec<Arc<Mutex<()>>>>,
pub vk_timestamp_compute_and_graphics: bool,
pub vk_timestamp_period_ns: f32,
pub legacy_frame_table: Mutex<Option<SharedContextFrameTable>>,
pub submission_worker: Arc<super::super::submission_worker::SubmissionWorker>,
}
pub(crate) struct RetainedVkCb {
pub command_buffer: vk::CommandBuffer,
pub used_slots: Vec<SlotKey>,
pub frame_table_row: Option<u32>,
pub last_signal_value: u64,
pub on_graphics_queue: bool,
}
impl LogicalDevice {
pub(crate) fn concurrent_queue_families(&self) -> Option<[u32; 2]> {
if self.compute_queue_family != self.queue_family {
Some([self.queue_family, self.compute_queue_family])
} else {
None
}
}
pub(crate) fn register_active_compute_queue_lock(&self, lock: Arc<Mutex<()>>) {
self.active_context_queue_locks.lock().unwrap().push(lock);
}
pub(crate) fn unregister_active_compute_queue_lock(&self, lock: &Arc<Mutex<()>>) {
let mut locks = self.active_context_queue_locks.lock().unwrap();
if let Some(i) = locks.iter().position(|l| Arc::ptr_eq(l, lock)) {
locks.swap_remove(i);
}
}
pub unsafe fn map_memory2(
&self,
memory: vk::DeviceMemory,
offset: vk::DeviceSize,
size: vk::DeviceSize,
) -> ash::prelude::VkResult<*mut core::ffi::c_void> {
let info = vk::MemoryMapInfoKHR::default().memory(memory).offset(offset).size(size);
let mut ptr = core::ptr::null_mut();
(self.map_memory2.fp().map_memory2_khr)(self.device.handle(), &info, &mut ptr).result_with_success(ptr)
}
pub unsafe fn unmap_memory2(&self, memory: vk::DeviceMemory) -> ash::prelude::VkResult<()> {
let info = vk::MemoryUnmapInfoKHR::default().memory(memory);
(self.map_memory2.fp().unmap_memory2_khr)(self.device.handle(), &info).result()
}
pub(crate) fn device_wait_idle_locked(&self) -> ash::prelude::VkResult<()> {
let _graphics_guard = self.queue_lock.lock().unwrap();
let arcs = {
let mut locks = self.active_context_queue_locks.lock().unwrap().clone();
locks.sort_by_key(|l| Arc::as_ptr(l) as usize);
locks
};
let mut compute_guards = Vec::with_capacity(arcs.len());
for arc in &arcs {
if !Arc::ptr_eq(arc, &self.queue_lock) {
compute_guards.push(arc.lock().unwrap());
}
}
unsafe { self.device.device_wait_idle() }
}
pub(crate) fn queues_wait_idle_locked(&self) -> ash::prelude::VkResult<()> {
let _graphics_guard = self.queue_lock.lock().unwrap();
let arcs = {
let mut locks = self.active_context_queue_locks.lock().unwrap().clone();
locks.sort_by_key(|l| Arc::as_ptr(l) as usize);
locks
};
let mut compute_guards = Vec::with_capacity(arcs.len());
for arc in &arcs {
if !Arc::ptr_eq(arc, &self.queue_lock) {
compute_guards.push(arc.lock().unwrap());
}
}
unsafe {
self.device.queue_wait_idle(self.queue)?;
for &q in &self.compute_queues {
self.device.queue_wait_idle(q)?;
}
}
Ok(())
}
pub(crate) fn synchronized_device_wait_idle(&self) -> ash::prelude::VkResult<()> {
let _ = self.submission_worker.flush();
let _ = self.submission_worker.check_error();
self.device_wait_idle_locked()
}
pub(crate) fn synchronized_queue_wait_idle(&self) -> ash::prelude::VkResult<()> {
let _ = self.submission_worker.flush();
let _ = self.submission_worker.check_error();
let _guard = self.queue_lock.lock().unwrap();
unsafe { self.device.queue_wait_idle(self.queue) }
}
pub(crate) fn synchronized_queue_submit(
&self,
submit_infos: &[vk::SubmitInfo],
fence: vk::Fence,
) -> ash::prelude::VkResult<()> {
let _ = self.submission_worker.flush();
let _ = self.submission_worker.check_error();
let _guard = self.queue_lock.lock().unwrap();
unsafe { self.device.queue_submit(self.queue, submit_infos, fence) }
}
pub(crate) fn synchronized_queue_submit2(
&self,
submit_infos: &[vk::SubmitInfo2],
fence: vk::Fence,
) -> ash::prelude::VkResult<()> {
let _ = self.submission_worker.flush();
let _ = self.submission_worker.check_error();
let _guard = self.queue_lock.lock().unwrap();
unsafe { self.device.queue_submit2(self.queue, submit_infos, fence) }
}
pub(crate) fn acquire_device_cmd_buffer(&self) -> anyhow::Result<vk::CommandBuffer> {
let mut out = self.allocate_device_cmd_buffers(1)?;
Ok(out.pop().unwrap())
}
pub(crate) fn allocate_device_cmd_buffers(&self, count: u32) -> anyhow::Result<Vec<vk::CommandBuffer>> {
if count == 0 {
return Ok(Vec::new());
}
let mut free = self.free_device_cmd_buffers.lock().unwrap();
let mut out = Vec::with_capacity(count as usize);
while out.len() < count as usize {
if let Some(cb) = free.pop() {
out.push(cb);
} else {
break;
}
}
let need = count as usize - out.len();
if need > 0 {
let alloc_info = vk::CommandBufferAllocateInfo::default()
.command_pool(self.command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(need as u32);
let cbs = unsafe { self.device.allocate_command_buffers(&alloc_info) }
.map_err(|e| anyhow::anyhow!("Failed to allocate device command buffers: {e:?}"))?;
out.extend(cbs);
}
Ok(out)
}
pub(crate) fn recycle_device_cmd_buffer(&self, cb: vk::CommandBuffer) {
self.free_device_cmd_buffers.lock().unwrap().push(cb);
}
pub(crate) fn recycle_device_cmd_buffers(&self, cbs: &[vk::CommandBuffer]) {
self.free_device_cmd_buffers.lock().unwrap().extend_from_slice(cbs);
}
pub(crate) fn free_device_cmd_buffers_now(&self, cbs: &[vk::CommandBuffer]) {
if cbs.is_empty() {
return;
}
let _guard = self.free_device_cmd_buffers.lock().unwrap();
unsafe {
self.device.free_command_buffers(self.command_pool, cbs);
}
}
}
#[derive(Clone)]
pub(crate) struct BufferState {
pub device_handle: DeviceHandle,
pub buffer: vk::Buffer,
pub memory: vk::DeviceMemory,
pub size: u64,
pub allocation_size: u64,
pub bindless_index: Option<u32>,
#[allow(dead_code)]
pub is_storage: bool,
#[allow(dead_code)]
pub element_stride: Option<u32>,
pub staging_buffer: Option<vk::Buffer>,
pub staging_memory: Option<vk::DeviceMemory>,
pub is_view: bool,
pub host_mapped: Option<usize>,
pub flags: crate::types::BufferFlags,
pub transient_heap_suballoc: bool,
pub view_byte_offset: Option<u64>,
pub is_sparse: bool,
pub sparse_block_size: u64,
pub sparse_pages: Vec<Option<(vk::DeviceMemory, vk::DeviceSize)>>,
pub is_withdraw_staging: bool,
pub texture_copy_footprint: Option<crate::backend::TextureCopyFootprint>,
}
pub(crate) struct ShaderState {
pub device_handle: DeviceHandle,
pub slang_source: String,
pub search_paths: Vec<String>,
pub defines: Vec<(String, String)>,
pub optimization_level: crate::types::OptimizationLevel,
pub vertex_module: Option<vk::ShaderModule>,
pub fragment_module: Option<vk::ShaderModule>,
pub compute_module: Option<vk::ShaderModule>,
pub reflection: Option<crate::slang::ShaderReflection>,
pub layout_checks: Vec<crate::slang::OwnedLayoutCheck>,
}
#[allow(dead_code)]
pub(crate) struct PipelineState {
pub device_handle: DeviceHandle,
pub pipeline: vk::Pipeline,
pub layout: vk::PipelineLayout,
pub owns_layout: bool,
pub parameter_block_layouts: Vec<crate::slang::ParameterBlockLayout>,
pub push_constant_categories: Vec<Option<crate::types::ResourceCategory>>,
pub binding_element_strides: Vec<Option<u32>>,
pub shader_debug_name: String,
}
#[allow(dead_code)]
pub(crate) struct ComputePipelineState {
pub device_handle: DeviceHandle,
pub pipeline: vk::Pipeline,
pub layout: vk::PipelineLayout,
pub owns_layout: bool,
pub parameter_block_layouts: Vec<crate::slang::ParameterBlockLayout>,
pub push_constant_categories: Vec<Option<crate::types::ResourceCategory>>,
pub binding_element_strides: Vec<Option<u32>>,
pub shader_debug_name: String,
}
pub(crate) struct RenderTargetState {
pub device_handle: DeviceHandle,
pub width: u32,
pub height: u32,
pub image: vk::Image,
pub image_memory: vk::DeviceMemory,
pub image_view: vk::ImageView,
pub depth_format: Option<DepthFormat>,
pub depth_image: Option<vk::Image>,
pub depth_memory: Option<vk::DeviceMemory>,
pub depth_view: Option<vk::ImageView>,
pub command_buffer: vk::CommandBuffer,
}
pub(crate) struct TextureState {
pub device_handle: DeviceHandle,
pub width: u32,
pub height: u32,
#[allow(dead_code)]
pub format: TextureFormat,
pub image: vk::Image,
pub memory: vk::DeviceMemory,
pub view: vk::ImageView,
pub staging_buffer: Option<vk::Buffer>,
pub staging_memory: Option<vk::DeviceMemory>,
pub bindless_index: Option<u32>,
pub sampled_bindless_index: Option<u32>,
pub current_layout: AtomicI32,
pub is_storage_image: bool,
pub transient_heap_suballoc: bool,
pub debug_name: Mutex<Option<String>>,
}
impl TextureState {
pub fn settled_shader_read_layout(&self) -> vk::ImageLayout {
if self.is_storage_image {
vk::ImageLayout::GENERAL
} else {
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
}
}
pub fn image_layout(&self) -> vk::ImageLayout {
vk::ImageLayout::from_raw(self.current_layout.load(Ordering::Relaxed))
}
pub fn set_image_layout(&self, layout: vk::ImageLayout) {
self.current_layout.store(layout.as_raw(), Ordering::Relaxed);
}
}
pub(crate) struct SamplerState {
pub device_handle: DeviceHandle,
pub sampler: vk::Sampler,
pub bindless_index: Option<u32>,
}
pub const MAX_FRAMES_IN_FLIGHT: usize = 3;
pub(crate) struct FrameSync {
pub command_buffer: vk::CommandBuffer,
pub copy_command_buffer: vk::CommandBuffer,
pub image_available_semaphore: vk::Semaphore,
pub work_done_semaphore: vk::Semaphore,
pub render_finished_semaphore: vk::Semaphore,
pub in_flight_fence: vk::Fence,
pub fence_pending: bool,
pub render_pass_submitted: bool,
pub frame_timeline_value: Option<u64>,
pub last_compute_timeline_value: u64,
pub copy_timeline_value: Option<u64>,
}
pub(crate) struct ScratchTextureSlot {
pub image: vk::Image,
pub memory: vk::DeviceMemory,
pub texture_handle: super::TextureHandle,
}
pub(crate) struct SurfaceState {
pub device_handle: DeviceHandle,
pub surface: vk::SurfaceKHR,
pub swapchain: vk::SwapchainKHR,
pub swapchain_images: Vec<vk::Image>,
pub swapchain_image_views: Vec<vk::ImageView>,
pub swapchain_prep_command_buffers: Vec<vk::CommandBuffer>,
pub swapchain_compute_present_command_buffers: Vec<vk::CommandBuffer>,
pub swapchain_render_present_command_buffers: Vec<vk::CommandBuffer>,
pub swapchain_texture_handles: Vec<super::TextureHandle>,
pub width: u32,
pub height: u32,
pub format: vk::Format,
pub present_mode: vk::PresentModeKHR,
pub present_mode_dirty: bool,
pub depth_format: Option<DepthFormat>,
pub depth_image: Option<vk::Image>,
pub depth_memory: Option<vk::DeviceMemory>,
pub depth_view: Option<vk::ImageView>,
pub current_frame: usize,
pub current_image_index: Option<u32>,
pub frame_sync: Vec<FrameSync>,
pub scratch_texture_slots: Vec<Option<ScratchTextureSlot>>,
pub current_texture_handle: Option<super::TextureHandle>,
pub frame_pending_gpu_commands: Vec<super::GpuCommand>,
pub pending_acquire_count: u32,
pub pending_swapchain_returns: Vec<(u32, crate::timeline::TimelineValue)>,
}
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) struct PendingBuffer {
pub buffer: BufferHandle,
pub slot: u32,
pub offset: u64,
}
pub(crate) struct PendingBufferGpuRelease {
pub retained_slots: Vec<SlotKey>,
pub buffer: vk::Buffer,
pub memory: vk::DeviceMemory,
pub staging_buffer: Option<vk::Buffer>,
pub staging_memory: Option<vk::DeviceMemory>,
pub sparse_teardown: Option<SparseBufferTeardown>,
}
#[allow(dead_code)]
pub(crate) struct SparseBufferTeardown {
pub allocation_size: u64,
pub block_size: u64,
pub binds: Vec<(u64, vk::DeviceMemory, vk::DeviceSize)>,
}
#[allow(dead_code)]
pub(crate) enum PendingDeletion {
Buffer {
buffer_handle: BufferHandle,
buffer: vk::Buffer,
memory: vk::DeviceMemory,
staging_buffer: Option<vk::Buffer>,
staging_memory: Option<vk::DeviceMemory>,
sparse_teardown: Option<SparseBufferTeardown>,
},
BufferView {
buffer_handle: BufferHandle,
},
ReplacedBufferGpu {
buffer: vk::Buffer,
memory: vk::DeviceMemory,
staging_buffer: Option<vk::Buffer>,
staging_memory: Option<vk::DeviceMemory>,
},
ReplacedSparseBufferGpu {
buffer: vk::Buffer,
allocation_size: u64,
block_size: u64,
binds: Vec<(u64, vk::DeviceMemory, vk::DeviceSize)>,
staging_buffer: Option<vk::Buffer>,
staging_memory: Option<vk::DeviceMemory>,
},
Texture {
texture_handle: TextureHandle,
image: vk::Image,
view: vk::ImageView,
memory: vk::DeviceMemory,
staging_buffer: Option<vk::Buffer>,
staging_memory: Option<vk::DeviceMemory>,
},
Sampler {
sampler: vk::Sampler,
},
}
pub(crate) struct DeletionQueue {
inner: super::super::shared::DeferredQueue<TimelineValue, PendingDeletion>,
}
impl DeletionQueue {
pub fn new() -> Self {
Self {
inner: super::super::shared::DeferredQueue::new(),
}
}
#[allow(dead_code, reason = "per-context deferred deletion API; device queue used today")]
pub fn queue(&mut self, barrier: TimelineValue, resource: PendingDeletion) {
self.inner.push(barrier, resource);
}
pub fn drain_up_to(&mut self, completed: TimelineValue) -> Vec<PendingDeletion> {
self.inner.drain_up_to(completed)
}
pub fn flush_all_drain(&mut self) -> Vec<PendingDeletion> {
self.inner.flush_all().collect()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
pub(crate) struct DeviceDeletionQueue {
inner: super::super::shared::DeferredQueue<Vec<(super::ContextHandle, u64)>, PendingDeletion>,
}
impl DeviceDeletionQueue {
pub fn new() -> Self {
Self {
inner: super::super::shared::DeferredQueue::new(),
}
}
pub fn queue(&mut self, requirements: Vec<(super::ContextHandle, u64)>, resource: PendingDeletion) {
self.inner.push(requirements, resource);
}
pub(crate) fn drain_ready(
&mut self,
completed_values: &HashMap<super::ContextHandle, u64>,
) -> Vec<PendingDeletion> {
self.inner
.drain_where(|reqs| slot_requirements_met(reqs, completed_values))
}
pub(crate) fn drain_everything(&mut self) -> Vec<PendingDeletion> {
self.inner.flush_all().collect()
}
pub(crate) fn pending_len(&self) -> usize {
self.inner.len()
}
}
pub(crate) fn destroy_pending_deletion(
ld: &LogicalDevice,
registry: &mut DescriptorRegistry,
resource: PendingDeletion,
) {
match resource {
PendingDeletion::Buffer {
buffer_handle,
buffer,
memory,
staging_buffer,
staging_memory,
sparse_teardown,
} => {
let retained_slots = registry.reclaim_buffer_slots(buffer_handle);
ld.pending_buffer_gpu_releases
.lock()
.unwrap()
.push(PendingBufferGpuRelease {
retained_slots,
buffer,
memory,
staging_buffer,
staging_memory,
sparse_teardown,
});
}
PendingDeletion::BufferView { buffer_handle } => {
registry.reclaim_buffer_slots(buffer_handle);
}
PendingDeletion::Texture { texture_handle, .. } => {
registry.reclaim_texture_slots(texture_handle);
destroy_pending_deletion_gpu(ld, resource);
}
other => destroy_pending_deletion_gpu(ld, other),
}
}
fn destroy_pending_deletion_gpu(ld: &LogicalDevice, resource: PendingDeletion) {
let device = &ld.device;
let bind_queue = ld.sparse_binding_queue;
let mut pool_guard = ld.sparse_page_pool.lock().unwrap();
unsafe {
match resource {
PendingDeletion::Buffer { .. } | PendingDeletion::BufferView { .. } => {}
PendingDeletion::ReplacedBufferGpu {
buffer,
memory,
staging_buffer,
staging_memory,
} => {
device.destroy_buffer(buffer, None);
device.free_memory(memory, None);
if let Some(buf) = staging_buffer {
device.destroy_buffer(buf, None);
}
if let Some(mem) = staging_memory {
device.free_memory(mem, None);
}
}
PendingDeletion::ReplacedSparseBufferGpu {
buffer,
allocation_size: _,
block_size,
binds,
staging_buffer,
staging_memory,
} => {
if !binds.is_empty() {
let mut sparse_binds = Vec::with_capacity(binds.len());
for (res_off, _mem, _mem_off) in &binds {
sparse_binds.push(
vk::SparseMemoryBind::default()
.resource_offset(*res_off)
.size(block_size)
.memory(vk::DeviceMemory::default())
.memory_offset(0)
.flags(vk::SparseMemoryBindFlags::empty()),
);
}
if let Err(e) =
super::sparse::queue_bind_sparse_sync(device, &ld.queue_lock, bind_queue, buffer, &sparse_binds)
{
tracing::warn!(?e, "sparse unbind on replaced buffer failed");
}
for (_res_off, mem, mem_off) in &binds {
if let Some(pool) = pool_guard.as_mut() {
pool.free_page(*mem, *mem_off);
}
}
}
device.destroy_buffer(buffer, None);
if let Some(buf) = staging_buffer {
device.destroy_buffer(buf, None);
}
if let Some(mem) = staging_memory {
device.free_memory(mem, None);
}
}
PendingDeletion::Texture {
texture_handle: _,
image,
view,
memory,
staging_buffer,
staging_memory,
} => {
device.destroy_image_view(view, None);
device.destroy_image(image, None);
device.free_memory(memory, None);
if let Some(buf) = staging_buffer {
device.destroy_buffer(buf, None);
}
if let Some(mem) = staging_memory {
device.free_memory(mem, None);
}
}
PendingDeletion::Sampler { sampler } => {
device.destroy_sampler(sampler, None);
}
}
}
}
fn release_buffer_gpu_resources(ld: &LogicalDevice, entry: PendingBufferGpuRelease) {
let PendingBufferGpuRelease {
retained_slots: _,
buffer,
memory,
staging_buffer,
staging_memory,
sparse_teardown,
} = entry;
let device = &ld.device;
let bind_queue = ld.sparse_binding_queue;
let mut pool_guard = ld.sparse_page_pool.lock().unwrap();
unsafe {
if let Some(td) = sparse_teardown {
if !td.binds.is_empty() {
let mut sparse_binds = Vec::with_capacity(td.binds.len());
for (res_off, _mem, _mem_off) in &td.binds {
sparse_binds.push(
vk::SparseMemoryBind::default()
.resource_offset(*res_off)
.size(td.block_size)
.memory(vk::DeviceMemory::default())
.memory_offset(0)
.flags(vk::SparseMemoryBindFlags::empty()),
);
}
if let Err(e) =
super::sparse::queue_bind_sparse_sync(device, &ld.queue_lock, bind_queue, buffer, &sparse_binds)
{
tracing::warn!(?e, "sparse unbind on buffer destroy failed");
}
for (_res_off, mem, mem_off) in &td.binds {
if let Some(pool) = pool_guard.as_mut() {
pool.free_page(*mem, *mem_off);
}
}
}
device.destroy_buffer(buffer, None);
} else {
device.destroy_buffer(buffer, None);
device.free_memory(memory, None);
}
if let Some(buf) = staging_buffer {
device.destroy_buffer(buf, None);
}
if let Some(mem) = staging_memory {
device.free_memory(mem, None);
}
}
}
impl LogicalDevice {
pub(crate) fn drain_deletion_queue_ready(
&self,
completed_values: &HashMap<super::ContextHandle, u64>,
) -> Vec<PendingDeletion> {
self.deletion_queue.lock().unwrap().drain_ready(completed_values)
}
pub(crate) fn process_deletion_queue_up_to(&self, completed_values: &HashMap<super::ContextHandle, u64>) {
let drained = self.drain_deletion_queue_ready(completed_values);
if !drained.is_empty() {
let descriptors_arc = Arc::clone(&self.descriptors);
let mut registry = descriptors_arc.lock().unwrap();
for r in drained {
destroy_pending_deletion(self, &mut registry, r);
}
}
let ready = {
let registry = self.descriptors.lock().unwrap();
self.take_ready_buffer_gpu_releases(®istry)
};
for entry in ready {
release_buffer_gpu_resources(self, entry);
}
}
pub(crate) fn take_ready_buffer_gpu_releases(&self, registry: &DescriptorRegistry) -> Vec<PendingBufferGpuRelease> {
let mut pending = self.pending_buffer_gpu_releases.lock().unwrap();
let mut ready = Vec::new();
let mut i = 0;
while i < pending.len() {
if registry.retained_pins_clear(&pending[i].retained_slots) {
ready.push(pending.swap_remove(i));
} else {
i += 1;
}
}
ready
}
pub(crate) fn process_deletion_queue_for_device(
&self,
contexts: &SharedContextMap,
device_handle: super::DeviceHandle,
) {
let completed_values = snapshot_context_completed_values(&self.device, contexts, device_handle);
self.process_deletion_queue_up_to(&completed_values);
}
pub(crate) fn flush_deletion_queue(&self) {
let batch = self.deletion_queue.lock().unwrap().drain_everything();
if !batch.is_empty() {
let descriptors_arc = Arc::clone(&self.descriptors);
let mut registry = descriptors_arc.lock().unwrap();
for r in batch {
destroy_pending_deletion(self, &mut registry, r);
}
}
let all = std::mem::take(&mut *self.pending_buffer_gpu_releases.lock().unwrap());
for entry in all {
release_buffer_gpu_resources(self, entry);
}
}
}
pub(crate) type SharedLogicalDevice = Arc<LogicalDevice>;
pub(crate) type SharedSubmissionContext = Arc<Mutex<SubmissionContext>>;
pub(super) type ComputeFencePoolEntry = (DeviceHandle, vk::Fence, Option<vk::CommandBuffer>);
pub(super) type ComputeFencePool = Mutex<HashMap<u64, ComputeFencePoolEntry>>;
pub(super) type SharedComputeFencePool = Arc<ComputeFencePool>;
pub(crate) type SharedContextMap = Arc<RwLock<HashMap<super::ContextHandle, SharedSubmissionContext>>>;
pub(crate) type SharedContextFrameTable = Arc<super::frame_table::ContextFrameTable>;
macro_rules! handle_table {
($table:ident, $shared:ident, $handle:ty, $value:ty) => {
#[derive(Default)]
pub(crate) struct $table {
pub entries: HashMap<$handle, $value>,
pub next_handle: $handle,
}
impl $table {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
next_handle: 1,
}
}
pub fn alloc_handle(&mut self) -> $handle {
let h = self.next_handle;
self.next_handle += 1;
h
}
}
pub(crate) type $shared = Arc<RwLock<$table>>;
};
}
handle_table!(BufferTable, SharedBufferTable, BufferHandle, BufferState);
handle_table!(ShaderTable, SharedShaderTable, ShaderHandle, ShaderState);
handle_table!(PipelineTable, SharedPipelineTable, PipelineHandle, PipelineState);
handle_table!(
ComputePipelineTable,
SharedComputePipelineTable,
ComputePipelineHandle,
ComputePipelineState
);
handle_table!(
RenderTargetTable,
SharedRenderTargetTable,
RenderTargetHandle,
RenderTargetState
);
handle_table!(TextureTable, SharedTextureTable, TextureHandle, TextureState);
handle_table!(SamplerTable, SharedSamplerTable, SamplerHandle, SamplerState);
pub(super) struct VulkanState {
pub entry: ash::Entry,
pub instance: ash::Instance,
pub physical_devices: Vec<PhysicalDeviceInfo>,
pub devices: HashMap<DeviceHandle, SharedLogicalDevice>,
pub next_device_handle: DeviceHandle,
pub contexts: SharedContextMap,
pub next_context_id: super::ContextHandle,
pub device_owner_handles: HashMap<super::DeviceHandle, super::ContextHandle>,
pub buffers: SharedBufferTable,
pub shaders: SharedShaderTable,
pub pipelines: SharedPipelineTable,
pub compute_pipelines: SharedComputePipelineTable,
pub render_targets: SharedRenderTargetTable,
pub surfaces: HashMap<SurfaceHandle, SurfaceState>,
pub next_surface_handle: SurfaceHandle,
pub textures: SharedTextureTable,
pub samplers: SharedSamplerTable,
pub slang_compiler: crate::slang::SlangCompiler,
pub compute_fence_pool: SharedComputeFencePool,
pub device_lost: std::sync::Arc<std::sync::atomic::AtomicBool>,
pub enable_validation: bool,
}