Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- `GarbageCollector` pool wrapper for retaining only cached acceleration structures, buffers, and
images that support recently observed requests.

## [0.14.5] - 2026-07-13

### Added
Expand Down
14 changes: 13 additions & 1 deletion guide/src/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ API docs: [`Graph::bind_resource`](https://docs.rs/vk-graph/latest/vk_graph/stru
[`Graph::resource`](https://docs.rs/vk-graph/latest/vk_graph/struct.Graph.html#method.resource),
[`Node`](https://docs.rs/vk-graph/latest/vk_graph/node/trait.Node.html),
[`Pool`](https://docs.rs/vk-graph/latest/vk_graph/pool/trait.Pool.html),
[`Cache`](https://docs.rs/vk-graph/latest/vk_graph/pool/cache/struct.Cache.html).
[`Cache`](https://docs.rs/vk-graph/latest/vk_graph/pool/cache/struct.Cache.html),
[`GarbageCollector`](https://docs.rs/vk-graph/latest/vk_graph/pool/garbage_collector/struct.GarbageCollector.html).

> [!CAUTION]
> All pipelines and resources (_buffers, images, and acceleration structures_) used in a `Graph`
Expand Down Expand Up @@ -66,6 +67,17 @@ over any `Pool`.
Cached resources let complex graphs reuse compatible resources while keeping the pooling strategy
separate from the reuse policy.

## Garbage Collection

The built-in pools may be wrapped in `GarbageCollector` to automatically remove cached acceleration
structures, buffers, and images that no longer match the application's requests. Successful requests
are observed until `GarbageCollector::collect_resources` is called. Collection retains resources
supporting those requests, then starts a new observation interval.

Calling `collect_resources` without making any requests clears all managed resources. Checked-out
resources remain valid during collection; a resource returning to a retained bucket is considered by
the next collection.

## Queue Sharing

`BufferInfo` and `ImageInfo` both expose `sharing_mode`.
Expand Down
125 changes: 108 additions & 17 deletions src/pool/fifo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use {
super::{
BufferHostMappingCompatibility, Cache, Lease, Pool, PoolConfig, compatible_buffer_info,
garbage_collector::{CollectResources, ResourceRequests},
with_cache,
},
crate::driver::{
Expand All @@ -19,6 +20,30 @@ use {
std::{collections::HashMap, sync::Arc},
};

fn compatible_accel_struct_info(
item_info: &AccelerationStructureInfo,
requested_info: &AccelerationStructureInfo,
) -> bool {
item_info.size >= requested_info.size
&& item_info.acceleration_structure_type == requested_info.acceleration_structure_type
}

fn compatible_fifo_image_info(item_info: &ImageInfo, requested_info: &ImageInfo) -> bool {
item_info.array_layer_count == requested_info.array_layer_count
&& item_info.alloc_dedicated == requested_info.alloc_dedicated
&& item_info.depth == requested_info.depth
&& item_info.format == requested_info.format
&& item_info.height == requested_info.height
&& item_info.mip_level_count == requested_info.mip_level_count
&& item_info.sample_count == requested_info.sample_count
&& item_info.sharing_mode == requested_info.sharing_mode
&& item_info.tiling == requested_info.tiling
&& item_info.image_type == requested_info.image_type
&& item_info.width == requested_info.width
&& item_info.flags.contains(requested_info.flags)
&& item_info.usage.contains(requested_info.usage)
}

/// A memory-efficient resource allocator.
///
/// The information for each resource request is compared against the stored resources for
Expand Down Expand Up @@ -116,6 +141,52 @@ impl FifoPool {
}
}

impl CollectResources for FifoPool {
fn collect_resources(&mut self, requests: &ResourceRequests) {
if requests.accel_structs.is_empty() {
self.clear_accel_structs();
} else {
with_cache(&self.accel_struct_cache, |cache| {
cache.retain(|item| {
requests
.accel_structs
.iter()
.any(|info| compatible_accel_struct_info(&item.info, info))
});
});
}

if requests.buffers.is_empty() {
self.clear_buffers();
} else {
with_cache(&self.buffer_cache, |cache| {
cache.retain(|item| {
requests.buffers.iter().any(|info| {
compatible_buffer_info(
&item.info,
info,
BufferHostMappingCompatibility::Exact,
)
})
});
});
}

if requests.images.is_empty() {
self.clear_images();
} else {
with_cache(&self.image_cache, |cache| {
cache.retain(|item| {
requests
.images
.iter()
.any(|info| compatible_fifo_image_info(&item.info, info))
});
});
}
}
}

impl Pool<AccelerationStructureInfo, AccelerationStructure> for FifoPool {
#[profiling::function]
fn resource(
Expand All @@ -131,9 +202,7 @@ impl Pool<AccelerationStructureInfo, AccelerationStructure> for FifoPool {
// Look for a compatible acceleration structure (big enough and same type)
for idx in 0..cache.len() {
let item = unsafe { cache.get_unchecked(idx) };
if item.info.size >= info.size
&& item.info.acceleration_structure_type == info.acceleration_structure_type
{
if compatible_accel_struct_info(&item.info, &info) {
let item = cache.swap_remove(idx);

return Some(Lease::new(cache_ref.clone(), item));
Expand Down Expand Up @@ -276,20 +345,7 @@ impl Pool<ImageInfo, Image> for FifoPool {
// usage flags)
for idx in 0..cache.len() {
let item = unsafe { cache.get_unchecked(idx) };
if item.info.array_layer_count == info.array_layer_count
&& item.info.alloc_dedicated == info.alloc_dedicated
&& item.info.depth == info.depth
&& item.info.format == info.format
&& item.info.height == info.height
&& item.info.mip_level_count == info.mip_level_count
&& item.info.sample_count == info.sample_count
&& item.info.sharing_mode == info.sharing_mode
&& item.info.tiling == info.tiling
&& item.info.image_type == info.image_type
&& item.info.width == info.width
&& item.info.flags.contains(info.flags)
&& item.info.usage.contains(info.usage)
{
if compatible_fifo_image_info(&item.info, &info) {
let item = cache.swap_remove(idx);

return Some(Lease::new(cache_ref.clone(), item));
Expand Down Expand Up @@ -332,3 +388,38 @@ impl Pool<RenderPassInfo, RenderPass> for FifoPool {
Ok(Lease::new(Arc::downgrade(cache_ref), item))
}
}

#[cfg(test)]
mod test {
use {
super::*,
crate::{
driver::device::{Device, DeviceInfo},
pool::garbage_collector::GarbageCollector,
},
ash::vk,
};

#[test]
#[ignore = "requires Vulkan device"]
fn vulkan_garbage_collector_retains_supported_fifo_resources() -> Result<(), DriverError> {
let device = Device::create(DeviceInfo::default())?;
let mut collector = GarbageCollector::new(FifoPool::with_capacity(&device, 4));
let retained_info = BufferInfo::device_mem(64, vk::BufferUsageFlags::TRANSFER_SRC);
let removed_info = BufferInfo::device_mem(64, vk::BufferUsageFlags::STORAGE_BUFFER);

drop(collector.resource(retained_info)?);
drop(collector.resource(removed_info)?);
collector.collect_resources();
assert_eq!(with_cache(&collector.buffer_cache, |cache| cache.len()), 2);

drop(collector.resource(retained_info)?);
collector.collect_resources();
assert_eq!(with_cache(&collector.buffer_cache, |cache| cache.len()), 1);

collector.collect_resources();
assert_eq!(with_cache(&collector.buffer_cache, |cache| cache.len()), 0);

Ok(())
}
}
Loading
Loading