concinnity-device 0.19.119

GPU backends (Metal, Vulkan, DirectX) behind a device facade for Concinnity
//! Descriptor sets for the shared fullscreen post passes, allocated per frame
//! rather than per pass.
//!
//! The pattern this replaces is a pool and a pre-wired set per frame in flight
//! owned by every effect, plus a `rewire_*` for each input that can change which
//! image it points at. That is where a Vulkan post pass grew its bulk and its
//! coupling: an effect had to be told about every other effect that might own its
//! scene input. A set allocated at encode time is written from what the pass
//! actually holds this frame, so there is nothing to rewire and no effect has to
//! know about another.
//!
//! One pool per frame in flight, reset when the frame comes round again. The
//! reset is safe without a fence of its own: `begin_frame` is called from the top
//! of the frame, after that slot's fence wait, so every set the pool handed out
//! last time round has retired.

use ash::vk;
use concinnity_core::render::error::{RenderError, RenderResult};
use std::sync::Mutex;

use crate::vulkan::descriptor_layout::PoolSizes;
use crate::vulkan::error::map_vk_result;
use crate::vulkan::owned::{OwnedDescriptorPool, VkDevice};
use crate::vulkan::resources::source_set_bindings;

// Sets one frame's post passes may allocate: one per draw. A frame with every
// pass on draws at most 24 (bloom's chain 11, SSGI 7, SSAO and the reflection
// composite 2 each, TAA and the SSR resolve 1 each), so this is double that.
const SETS_PER_FRAME: u32 = 48;

// Sources one set may hold, each an image and a sampler. The widest post pass
// binds a handful, so eight covers every one of them.
const SOURCES_PER_SET: u32 = 8;

// A per-frame descriptor pool ring for the shared post passes.
pub(in crate::vulkan) struct PostSetArena {
    // One pool per frame in flight, plus the frame tick it was last reset on.
    // `Mutex` because the graph executor allocates from worker threads holding
    // `&self`, and Vulkan requires external synchronization over a pool.
    slots: Vec<Mutex<PoolSlot>>,
}

struct PoolSlot {
    pool: OwnedDescriptorPool,
}

impl PostSetArena {
    // A pool per frame in flight.
    pub(in crate::vulkan) fn new(device: &VkDevice, frames: usize) -> RenderResult<Self> {
        let sizes = PoolSizes::default()
            .sets(&source_set_bindings(SOURCES_PER_SET), SETS_PER_FRAME)
            .build();
        let mut slots = Vec::with_capacity(frames.max(1));
        for _ in 0..frames.max(1) {
            let pool = device
                .create_descriptor_pool(
                    &vk::DescriptorPoolCreateInfo::default()
                        .pool_sizes(&sizes)
                        .max_sets(SETS_PER_FRAME),
                )
                .map_err(|e| map_vk_result(e, "post descriptor pool"))?;
            slots.push(Mutex::new(PoolSlot { pool }));
        }
        Ok(Self { slots })
    }

    // Reclaim frame slot `frame`'s sets. Called from the top of the frame, after
    // that slot's fence wait, which is what makes the reclaim legal.
    pub(in crate::vulkan) fn begin_frame(&self, device: &VkDevice, frame: usize) {
        let Ok(slot) = self.slots[frame % self.slots.len()].lock() else {
            tracing::error!("post descriptor arena poisoned");
            return;
        };
        // SAFETY: the caller waited on this frame slot's fence, so every set this pool handed out
        // on its previous pass has retired; the pool belongs to this device and the lock makes this
        // the only thread touching it.
        let reset = unsafe {
            device.reset_descriptor_pool(slot.pool.handle(), vk::DescriptorPoolResetFlags::empty())
        };
        if let Err(e) = reset {
            tracing::error!("post descriptor pool reset: {e}");
        }
    }

    // One set of `layout` from `frame`'s pool.
    pub(in crate::vulkan) fn alloc(
        &self,
        device: &VkDevice,
        frame: usize,
        layout: vk::DescriptorSetLayout,
    ) -> RenderResult<vk::DescriptorSet> {
        let slot = self.slots[frame % self.slots.len()]
            .lock()
            .map_err(|_| RenderError::Other("post descriptor arena poisoned".to_string()))?;
        let layouts = [layout];
        let sets =
            crate::vulkan::resources::alloc_descriptor_sets(device, slot.pool.handle(), &layouts)?;
        sets.into_iter()
            .next()
            .ok_or_else(|| RenderError::Other("post descriptor arena returned no set".to_string()))
    }
}