use ash::vk;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::post::device::PostLoadOp;
use concinnity_core::render::render_graph::PixelFormat;
use std::sync::Mutex;
use crate::vulkan::owned::{OwnedFramebuffer, OwnedRenderPass, VkDevice};
use crate::vulkan::transient_pool::image_format;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(in crate::vulkan) enum AttachmentRest {
Sampled,
Graph,
}
#[derive(Copy, Clone, PartialEq, Eq)]
struct PassKey {
format: PixelFormat,
load: PostLoadOp,
rest: AttachmentRest,
}
fn attachment_open(
load: PostLoadOp,
rest: AttachmentRest,
) -> (vk::AttachmentLoadOp, vk::ImageLayout) {
let op = match load {
PostLoadOp::DontCare => vk::AttachmentLoadOp::DONT_CARE,
PostLoadOp::Load => vk::AttachmentLoadOp::LOAD,
};
let layout = match (rest, load) {
(AttachmentRest::Graph, _) => vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
(AttachmentRest::Sampled, PostLoadOp::DontCare) => vk::ImageLayout::UNDEFINED,
(AttachmentRest::Sampled, PostLoadOp::Load) => vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
};
(op, layout)
}
fn attachment_close(rest: AttachmentRest) -> vk::ImageLayout {
match rest {
AttachmentRest::Sampled => vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
AttachmentRest::Graph => vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
}
}
fn attachment_access() -> vk::AccessFlags {
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::COLOR_ATTACHMENT_READ
| vk::AccessFlags::SHADER_READ
}
fn create_render_pass(device: &VkDevice, key: PassKey) -> RenderResult<OwnedRenderPass> {
let PassKey { format, load, rest } = key;
let (load_op, initial) = attachment_open(load, rest);
let attachment = vk::AttachmentDescription::default()
.format(image_format(format))
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(load_op)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(initial)
.final_layout(attachment_close(rest));
let color_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color_ref));
let dependency = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.dst_access_mask(attachment_access());
let info = vk::RenderPassCreateInfo::default()
.attachments(std::slice::from_ref(&attachment))
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dependency));
device
.create_render_pass(&info)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "post render pass"))
}
#[derive(Default)]
pub(in crate::vulkan) struct PostPassCache {
passes: Mutex<Vec<(PassKey, OwnedRenderPass)>>,
framebuffers: Mutex<Vec<(vk::ImageView, vk::RenderPass, OwnedFramebuffer)>>,
}
impl PostPassCache {
pub(in crate::vulkan) fn new() -> Self {
Self::default()
}
pub(in crate::vulkan) fn render_pass(
&self,
device: &VkDevice,
format: PixelFormat,
load: PostLoadOp,
rest: AttachmentRest,
) -> RenderResult<vk::RenderPass> {
let key = PassKey { format, load, rest };
let mut passes = self
.passes
.lock()
.map_err(|_| RenderError::Other("post render-pass cache poisoned".to_string()))?;
if let Some((_, rp)) = passes.iter().find(|(k, _)| *k == key) {
return Ok(rp.handle());
}
let rp = create_render_pass(device, key)?;
let handle = rp.handle();
passes.push((key, rp));
Ok(handle)
}
pub(in crate::vulkan) fn framebuffer(
&self,
device: &VkDevice,
render_pass: vk::RenderPass,
view: vk::ImageView,
extent: vk::Extent2D,
) -> RenderResult<vk::Framebuffer> {
let mut fbs = self
.framebuffers
.lock()
.map_err(|_| RenderError::Other("post framebuffer cache poisoned".to_string()))?;
if let Some((_, _, fb)) = fbs
.iter()
.find(|(v, rp, _)| *v == view && *rp == render_pass)
{
return Ok(fb.handle());
}
let fb = device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(render_pass)
.attachments(std::slice::from_ref(&view))
.width(extent.width)
.height(extent.height)
.layers(1),
)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "post framebuffer"))?;
let handle = fb.handle();
fbs.push((view, render_pass, fb));
Ok(handle)
}
pub(in crate::vulkan) fn forget_views(&self) {
if let Ok(mut fbs) = self.framebuffers.lock() {
fbs.clear();
}
}
pub(in crate::vulkan) fn destroy(&self) {
self.forget_views();
if let Ok(mut passes) = self.passes.lock() {
passes.clear();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_preserving_load_begins_readable_and_a_discarding_one_undefined() {
assert_eq!(
attachment_open(PostLoadOp::Load, AttachmentRest::Sampled),
(
vk::AttachmentLoadOp::LOAD,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
)
);
assert_eq!(
attachment_open(PostLoadOp::DontCare, AttachmentRest::Sampled),
(vk::AttachmentLoadOp::DONT_CARE, vk::ImageLayout::UNDEFINED)
);
assert_eq!(
attachment_close(AttachmentRest::Sampled),
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
);
}
#[test]
fn a_graph_driven_attachment_stays_in_the_attachment_layout() {
for load in [PostLoadOp::DontCare, PostLoadOp::Load] {
assert_eq!(
attachment_open(load, AttachmentRest::Graph).1,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL
);
}
assert_eq!(
attachment_close(AttachmentRest::Graph),
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL
);
}
#[test]
fn the_dependency_declares_the_read_a_load_performs() {
let access = attachment_access();
assert!(access.contains(vk::AccessFlags::COLOR_ATTACHMENT_READ));
assert!(access.contains(vk::AccessFlags::COLOR_ATTACHMENT_WRITE));
assert!(access.contains(vk::AccessFlags::SHADER_READ));
}
}