#[cfg(feature = "fsr2")]
use anyhow::{anyhow, bail};
use anyhow::Result;
use ash::vk;
use crate::{Allocator, DefaultAllocator, Error, PhysicalResourceBindings, VirtualResource};
#[cfg(feature = "fsr2")]
use crate::{ComputeSupport, Device, ImageView};
use crate::command_buffer::IncompleteCommandBuffer;
#[cfg(feature = "fsr2")]
use crate::fsr2::{Fsr2DispatchDescription, Fsr2DispatchResources};
use crate::graph::pass_graph::PassResource;
#[cfg(feature = "fsr2")]
use crate::graph::physical_resource::PhysicalResource;
use crate::graph::resource::{AttachmentType, ResourceUsage};
use crate::pipeline::PipelineStage;
use crate::pool::LocalPool;
use crate::sync::domain::ExecutionDomain;
use crate::util::to_vk::IntoVulkanType;
pub type PassFnResult<'q, D, A> = Result<IncompleteCommandBuffer<'q, D, A>>;
pub trait PassExecutor<D: ExecutionDomain, U, A: Allocator> {
fn execute<'q>(
&mut self,
cmd: IncompleteCommandBuffer<'q, D, A>,
local_pool: &mut LocalPool<A>,
bindings: &PhysicalResourceBindings,
user_data: &mut U,
) -> PassFnResult<'q, D, A>;
}
impl<D, U, A, F> PassExecutor<D, U, A> for F
where
D: ExecutionDomain,
A: Allocator,
F: for<'q> FnMut(
IncompleteCommandBuffer<'q, D, A>,
&mut LocalPool<A>,
&PhysicalResourceBindings,
&mut U,
) -> PassFnResult<'q, D, A>,
{
fn execute<'q>(
&mut self,
cmd: IncompleteCommandBuffer<'q, D, A>,
local_pool: &mut LocalPool<A>,
bindings: &PhysicalResourceBindings,
user_data: &mut U,
) -> PassFnResult<'q, D, A> {
self(cmd, local_pool, bindings, user_data)
}
}
pub(crate) type BoxedPassFn<'cb, D, U, A> = Box<dyn PassExecutor<D, U, A> + 'cb>;
pub struct EmptyPassExecutor;
impl EmptyPassExecutor {
pub fn new() -> Self {
Self {}
}
pub fn new_boxed() -> Box<Self> {
Box::new(Self::new())
}
}
impl<D: ExecutionDomain, U, A: Allocator> PassExecutor<D, U, A> for EmptyPassExecutor {
fn execute<'q>(
&mut self,
cmd: IncompleteCommandBuffer<'q, D, A>,
_local_pool: &mut LocalPool<A>,
_bindings: &PhysicalResourceBindings,
_user_data: &mut U,
) -> PassFnResult<'q, D, A> {
Ok(cmd)
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Pass<'cb, D: ExecutionDomain, U = (), A: Allocator = DefaultAllocator> {
pub(crate) name: String,
pub(crate) color: Option<[f32; 4]>,
pub(crate) inputs: Vec<PassResource>,
pub(crate) outputs: Vec<PassResource>,
#[derivative(Debug = "ignore")]
pub(crate) execute: BoxedPassFn<'cb, D, U, A>,
pub(crate) is_renderpass: bool,
}
#[derive(Copy, Clone, Debug)]
pub enum ClearColor {
Float([f32; 4]),
Int([i32; 4]),
Uint([u32; 4]),
}
#[derive(Copy, Clone, Default, Debug)]
pub struct ClearDepthStencil {
pub depth: f32,
pub stencil: u32,
}
impl IntoVulkanType for ClearColor {
type Output = vk::ClearColorValue;
fn into_vulkan(self) -> Self::Output {
match self {
ClearColor::Float(values) => vk::ClearColorValue {
float32: values,
},
ClearColor::Int(values) => vk::ClearColorValue {
int32: values,
},
ClearColor::Uint(values) => vk::ClearColorValue {
uint32: values,
},
}
}
}
impl IntoVulkanType for ClearDepthStencil {
type Output = vk::ClearDepthStencilValue;
fn into_vulkan(self) -> Self::Output {
vk::ClearDepthStencilValue {
depth: self.depth,
stencil: self.stencil,
}
}
}
pub struct PassBuilder<'cb, D: ExecutionDomain, U = (), A: Allocator = DefaultAllocator> {
inner: Pass<'cb, D, U, A>,
}
impl<'cb, D: ExecutionDomain, U, A: Allocator> Pass<'cb, D, U, A> {
pub fn output(&self, resource: &VirtualResource) -> Option<&VirtualResource> {
self.outputs
.iter()
.filter_map(|output| {
if resource.is_associated_with(&output.resource) {
Some(&output.resource)
} else {
None
}
})
.next()
}
pub fn name(&self) -> &str {
&self.name
}
}
impl<'cb, D: ExecutionDomain, U, A: Allocator> PassBuilder<'cb, D, U, A> {
pub fn new(name: impl Into<String>) -> Self {
PassBuilder {
inner: Pass {
name: name.into(),
color: None,
execute: EmptyPassExecutor::new_boxed(),
inputs: vec![],
outputs: vec![],
is_renderpass: false,
},
}
}
pub fn render(name: impl Into<String>) -> Self {
PassBuilder {
inner: Pass {
name: name.into(),
color: None,
execute: EmptyPassExecutor::new_boxed(),
inputs: vec![],
outputs: vec![],
is_renderpass: true,
},
}
}
pub fn present(name: impl Into<String>, swapchain: &VirtualResource) -> Pass<'cb, D, U, A> {
Pass {
name: name.into(),
color: None,
inputs: vec![PassResource {
usage: ResourceUsage::Present,
resource: swapchain.clone(),
stage: PipelineStage::BOTTOM_OF_PIPE,
layout: vk::ImageLayout::PRESENT_SRC_KHR,
clear_value: None,
load_op: None,
}],
outputs: vec![],
execute: EmptyPassExecutor::new_boxed(),
is_renderpass: false,
}
}
#[cfg(feature = "debug-markers")]
pub fn color(mut self, color: [f32; 4]) -> Self {
self.inner.color = Some(color);
self
}
pub fn color_attachment(
mut self,
resource: &VirtualResource,
op: vk::AttachmentLoadOp,
clear: Option<vk::ClearColorValue>,
) -> Result<Self> {
if !self.inner.is_renderpass {
return Err(Error::Uncategorized(
"Cannot attach color attachment to a pass that is not a renderpass",
)
.into());
}
if op == vk::AttachmentLoadOp::CLEAR && clear.is_none() {
return Err(anyhow::Error::from(Error::NoClearValue));
}
self.inner.inputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Color),
resource: resource.clone(),
stage: match op {
vk::AttachmentLoadOp::LOAD => PipelineStage::COLOR_ATTACHMENT_OUTPUT,
vk::AttachmentLoadOp::CLEAR => PipelineStage::COLOR_ATTACHMENT_OUTPUT,
_ => todo!(),
},
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
clear_value: None,
load_op: None,
});
self.inner.outputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Color),
resource: resource.upgrade(),
stage: PipelineStage::COLOR_ATTACHMENT_OUTPUT,
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
clear_value: clear.map(|c| vk::ClearValue {
color: c,
}),
load_op: Some(op),
});
Ok(self)
}
pub fn clear_color_attachment(
self,
resource: &VirtualResource,
color: ClearColor,
) -> Result<Self> {
self.color_attachment(resource, vk::AttachmentLoadOp::CLEAR, Some(color.into_vulkan()))
}
pub fn load_color_attachment(self, resource: &VirtualResource) -> Result<Self> {
self.color_attachment(resource, vk::AttachmentLoadOp::LOAD, None)
}
pub fn clear_depth_attachment(
self,
resource: &VirtualResource,
clear: ClearDepthStencil,
) -> Result<Self> {
self.depth_attachment(resource, vk::AttachmentLoadOp::CLEAR, Some(clear.into_vulkan()))
}
pub fn load_depth_attachment(self, resource: &VirtualResource) -> Result<Self> {
self.depth_attachment(resource, vk::AttachmentLoadOp::LOAD, None)
}
pub fn depth_attachment(
mut self,
resource: &VirtualResource,
op: vk::AttachmentLoadOp,
clear: Option<vk::ClearDepthStencilValue>,
) -> Result<Self> {
if !self.inner.is_renderpass {
return Err(Error::Uncategorized(
"Cannot attach depth attachment to a pass that is not a renderpass",
)
.into());
}
self.inner.inputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Depth),
resource: resource.clone(),
stage: match op {
vk::AttachmentLoadOp::LOAD => PipelineStage::EARLY_FRAGMENT_TESTS,
vk::AttachmentLoadOp::CLEAR => PipelineStage::EARLY_FRAGMENT_TESTS,
_ => todo!(),
},
layout: vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL,
clear_value: None,
load_op: None,
});
self.inner.outputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Depth),
resource: resource.upgrade(),
stage: PipelineStage::LATE_FRAGMENT_TESTS,
layout: vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL,
clear_value: clear.map(|c| vk::ClearValue {
depth_stencil: c,
}),
load_op: Some(op),
});
Ok(self)
}
pub fn resolve(mut self, src: &VirtualResource, dst: &VirtualResource) -> Self {
self.inner.inputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Resolve(src.clone())),
resource: dst.clone(),
stage: PipelineStage::COLOR_ATTACHMENT_OUTPUT, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
clear_value: None,
load_op: None,
});
self.inner.outputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Resolve(src.clone())),
resource: dst.upgrade(),
stage: PipelineStage::COLOR_ATTACHMENT_OUTPUT, layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
clear_value: None,
load_op: Some(vk::AttachmentLoadOp::DONT_CARE),
});
self
}
pub fn resolve_depth(mut self, src: &VirtualResource, dst: &VirtualResource) -> Self {
self.inner.inputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Resolve(src.clone())),
resource: dst.clone(),
stage: PipelineStage::COLOR_ATTACHMENT_OUTPUT, layout: vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL,
clear_value: None,
load_op: None,
});
self.inner.outputs.push(PassResource {
usage: ResourceUsage::Attachment(AttachmentType::Resolve(src.clone())),
resource: dst.upgrade(),
stage: PipelineStage::COLOR_ATTACHMENT_OUTPUT, layout: vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL,
clear_value: None,
load_op: Some(vk::AttachmentLoadOp::DONT_CARE),
});
self
}
pub fn sample_image(mut self, resource: &VirtualResource, stage: PipelineStage) -> Self {
self.inner.inputs.push(PassResource {
usage: ResourceUsage::ShaderRead,
resource: resource.clone(),
stage,
layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
clear_value: None,
load_op: None,
});
self
}
pub fn write_storage_image(mut self, resource: &VirtualResource, stage: PipelineStage) -> Self {
self.inner.inputs.push(PassResource {
usage: ResourceUsage::ShaderWrite,
resource: resource.clone(),
stage,
layout: vk::ImageLayout::GENERAL,
clear_value: None,
load_op: None,
});
self.inner.outputs.push(PassResource {
usage: ResourceUsage::ShaderWrite,
resource: resource.upgrade(),
stage,
layout: vk::ImageLayout::GENERAL,
clear_value: None,
load_op: None,
});
self
}
pub fn read_storage_image(mut self, resource: &VirtualResource, stage: PipelineStage) -> Self {
self.inner.inputs.push(PassResource {
usage: ResourceUsage::ShaderRead,
resource: resource.clone(),
stage,
layout: vk::ImageLayout::GENERAL,
clear_value: None,
load_op: None,
});
self
}
#[allow(dead_code)]
fn sample_optional_image(
self,
resource: &Option<VirtualResource>,
stage: PipelineStage,
) -> Self {
match resource {
None => self,
Some(resource) => self.sample_image(resource, stage),
}
}
pub fn executor(mut self, exec: impl PassExecutor<D, U, A> + 'cb) -> Self {
self.inner.execute = Box::new(exec);
self
}
pub fn execute_fn<F>(mut self, exec: F) -> Self
where
F: for<'q> FnMut(
IncompleteCommandBuffer<'q, D, A>,
&mut LocalPool<A>,
&PhysicalResourceBindings,
&mut U,
) -> PassFnResult<'q, D, A>
+ 'cb, {
self.inner.execute = Box::new(exec);
self
}
pub fn build(self) -> Pass<'cb, D, U, A> {
self.inner
}
}
#[cfg(feature = "fsr2")]
#[derive(Debug, Clone)]
pub struct Fsr2DispatchVirtualResources {
pub color: VirtualResource,
pub depth: VirtualResource,
pub motion_vectors: VirtualResource,
pub exposure: Option<VirtualResource>,
pub reactive: Option<VirtualResource>,
pub transparency_and_composition: Option<VirtualResource>,
pub output: VirtualResource,
}
#[cfg(feature = "fsr2")]
impl Fsr2DispatchVirtualResources {
fn resolve_image_resource(
resource: &VirtualResource,
bindings: &PhysicalResourceBindings,
) -> Result<ImageView> {
let resolved = bindings.resolve(resource).ok_or_else(|| {
anyhow!("Missing physical binding for required FSR2 resource: {}", resource.name())
})?;
let PhysicalResource::Image(image) = &resolved else {
bail!("FSR2 resource {} should be an image", resource.name());
};
Ok(image.clone())
}
fn resolve_optional_image_resource(
resource: &Option<VirtualResource>,
bindings: &PhysicalResourceBindings,
) -> Result<Option<ImageView>> {
match resource {
None => Ok(None),
Some(resource) => Ok(Some(Self::resolve_image_resource(resource, bindings)?)),
}
}
pub fn resolve(&self, bindings: &PhysicalResourceBindings) -> Result<Fsr2DispatchResources> {
Ok(Fsr2DispatchResources {
color: Self::resolve_image_resource(&self.color, bindings)?,
depth: Self::resolve_image_resource(&self.depth, bindings)?,
motion_vectors: Self::resolve_image_resource(&self.motion_vectors, bindings)?,
exposure: Self::resolve_optional_image_resource(&self.exposure, bindings)?,
reactive: Self::resolve_optional_image_resource(&self.reactive, bindings)?,
transparency_and_composition: Self::resolve_optional_image_resource(
&self.transparency_and_composition,
bindings,
)?,
output: Self::resolve_image_resource(&self.output, bindings)?,
})
}
}
#[cfg(feature = "fsr2")]
impl<'cb, D: ExecutionDomain + ComputeSupport, U, A: Allocator> PassBuilder<'cb, D, U, A> {
pub fn fsr2(
device: Device,
descr: Fsr2DispatchDescription,
resources: Fsr2DispatchVirtualResources,
) -> Pass<'cb, D, U, A> {
let pass = PassBuilder::<'cb, D, U, A>::new("fsr2")
.sample_image(&resources.color, PipelineStage::COMPUTE_SHADER)
.sample_image(&resources.motion_vectors, PipelineStage::COMPUTE_SHADER)
.sample_image(&resources.depth, PipelineStage::COMPUTE_SHADER)
.sample_optional_image(&resources.exposure, PipelineStage::COMPUTE_SHADER)
.sample_optional_image(&resources.reactive, PipelineStage::COMPUTE_SHADER)
.sample_optional_image(
&resources.transparency_and_composition,
PipelineStage::COMPUTE_SHADER,
)
.write_storage_image(&resources.output, PipelineStage::COMPUTE_SHADER)
.execute_fn(move |cmd, _, bindings, _| {
let resolved_resources = resources.resolve(bindings)?;
let mut fsr2 = device.fsr2_context();
fsr2.dispatch(&descr, &resolved_resources, &cmd)?;
Ok(cmd)
})
.build();
pass
}
}