use super::RenderPass;
use crate::{
device::{Device, DeviceOwned, DeviceOwnedDebugWrapper},
image::{
view::{ImageView, ImageViewType},
ImageAspects, ImageType, ImageUsage,
},
macros::{impl_id_counter, vulkan_bitflags},
Validated, ValidationError, VulkanError, VulkanObject,
};
use smallvec::SmallVec;
use std::{mem::MaybeUninit, num::NonZeroU64, ops::Range, ptr, sync::Arc};
#[derive(Debug)]
pub struct Framebuffer {
handle: ash::vk::Framebuffer,
render_pass: DeviceOwnedDebugWrapper<Arc<RenderPass>>,
id: NonZeroU64,
flags: FramebufferCreateFlags,
attachments: Vec<DeviceOwnedDebugWrapper<Arc<ImageView>>>,
extent: [u32; 2],
layers: u32,
}
impl Framebuffer {
pub fn new(
render_pass: Arc<RenderPass>,
mut create_info: FramebufferCreateInfo,
) -> Result<Arc<Framebuffer>, Validated<VulkanError>> {
create_info.set_auto_extent_layers(&render_pass);
Self::validate_new(&render_pass, &create_info)?;
unsafe { Ok(Self::new_unchecked(render_pass, create_info)?) }
}
fn validate_new(
render_pass: &RenderPass,
create_info: &FramebufferCreateInfo,
) -> Result<(), Box<ValidationError>> {
create_info
.validate(render_pass.device())
.map_err(|err| err.add_context("create_info"))?;
let &FramebufferCreateInfo {
flags: _,
ref attachments,
extent,
layers,
_ne,
} = create_info;
if attachments.len() != render_pass.attachments().len() {
return Err(Box::new(ValidationError {
problem: "`create_info.attachments` does not have the same length as \
`render_pass.attachments()`"
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-attachmentCount-00876"],
..Default::default()
}));
}
for (index, ((image_view, attachment_desc), attachment_use)) in attachments
.iter()
.zip(render_pass.attachments())
.zip(&render_pass.attachment_use)
.enumerate()
{
if attachment_use.color_attachment
&& !image_view.usage().intersects(ImageUsage::COLOR_ATTACHMENT)
{
return Err(Box::new(ValidationError {
problem: format!(
"`render_pass` uses `create_info.attachments[{}]` as \
a color attachment, but it was not created with the \
`ImageUsage::COLOR_ATTACHMENT` usage",
index
)
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-00877"],
..Default::default()
}));
}
if attachment_use.depth_stencil_attachment
&& !image_view
.usage()
.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT)
{
return Err(Box::new(ValidationError {
problem: format!(
"`render_pass` uses `create_info.attachments[{}]` as \
a depth or stencil attachment, but it was not created with the \
`ImageUsage::DEPTH_STENCIL_ATTACHMENT` usage",
index,
)
.into(),
vuids: &[
"VUID-VkFramebufferCreateInfo-pAttachments-02633",
"VUID-VkFramebufferCreateInfo-pAttachments-02634",
],
..Default::default()
}));
}
if attachment_use.input_attachment
&& !image_view.usage().intersects(ImageUsage::INPUT_ATTACHMENT)
{
return Err(Box::new(ValidationError {
problem: format!(
"`render_pass` uses `create_info.attachments[{}]` as \
an input attachment, but it was not created with the \
`ImageUsage::INPUT_ATTACHMENT` usage",
index,
)
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-02633"],
..Default::default()
}));
}
if image_view.format() != attachment_desc.format {
return Err(Box::new(ValidationError {
problem: format!(
"the format of `create_info.attachments[{}]` does not equal \
`render_pass.attachments()[{0}].format`",
index,
)
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-00880"],
..Default::default()
}));
}
if image_view.image().samples() != attachment_desc.samples {
return Err(Box::new(ValidationError {
problem: format!(
"the samples of `create_info.attachments[{}]` does not equal \
`render_pass.attachments()[{0}].samples`",
index,
)
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-00881"],
..Default::default()
}));
}
let image_view_extent = image_view.image().extent();
let image_view_array_layers = image_view.subresource_range().array_layers.end
- image_view.subresource_range().array_layers.start;
if attachment_use.input_attachment
|| attachment_use.color_attachment
|| attachment_use.depth_stencil_attachment
{
if image_view_extent[0] < extent[0] || image_view_extent[1] < extent[1] {
return Err(Box::new(ValidationError {
problem: format!(
"`render_pass` uses `create_info.attachments[{}]` as an input, color, \
depth or stencil attachment, but \
its width and height are less than `create_info.extent`",
index,
)
.into(),
vuids: &[
"VUID-VkFramebufferCreateInfo-flags-04533",
"VUID-VkFramebufferCreateInfo-flags-04534",
],
..Default::default()
}));
}
if image_view_array_layers < layers {
return Err(Box::new(ValidationError {
problem: format!(
"`render_pass` uses `create_info.attachments[{}]` as an input, color, \
depth or stencil attachment, but \
its layer count is less than `create_info.layers`",
index,
)
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-flags-04535"],
..Default::default()
}));
}
if image_view_array_layers < render_pass.views_used() {
return Err(Box::new(ValidationError {
problem: format!(
"`render_pass` has multiview enabled, and uses \
`create_info.attachments[{}]` as an input, color, depth or stencil \
attachment, but its layer count is less than the number of views used \
by `render_pass`",
index
)
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-renderPass-04536"],
..Default::default()
}));
}
}
if render_pass.views_used() != 0 && layers != 1 {
return Err(Box::new(ValidationError {
problem: "`render_pass` has multiview enabled, but \
`create_info.layers` is not 1"
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-renderPass-02531"],
..Default::default()
}));
}
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn new_unchecked(
render_pass: Arc<RenderPass>,
mut create_info: FramebufferCreateInfo,
) -> Result<Arc<Framebuffer>, VulkanError> {
create_info.set_auto_extent_layers(&render_pass);
let &FramebufferCreateInfo {
flags,
ref attachments,
extent,
layers,
_ne: _,
} = &create_info;
let attachments_vk: SmallVec<[_; 4]> =
attachments.iter().map(VulkanObject::handle).collect();
let create_info_vk = ash::vk::FramebufferCreateInfo {
flags: flags.into(),
render_pass: render_pass.handle(),
attachment_count: attachments_vk.len() as u32,
p_attachments: attachments_vk.as_ptr(),
width: extent[0],
height: extent[1],
layers,
..Default::default()
};
let handle = unsafe {
let fns = render_pass.device().fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.create_framebuffer)(
render_pass.device().handle(),
&create_info_vk,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Self::from_handle(render_pass, handle, create_info))
}
#[inline]
pub unsafe fn from_handle(
render_pass: Arc<RenderPass>,
handle: ash::vk::Framebuffer,
mut create_info: FramebufferCreateInfo,
) -> Arc<Framebuffer> {
create_info.set_auto_extent_layers(&render_pass);
let FramebufferCreateInfo {
flags,
attachments,
extent,
layers,
_ne: _,
} = create_info;
Arc::new(Framebuffer {
handle,
render_pass: DeviceOwnedDebugWrapper(render_pass),
id: Self::next_id(),
flags,
attachments: attachments
.into_iter()
.map(DeviceOwnedDebugWrapper)
.collect(),
extent,
layers,
})
}
#[inline]
pub fn render_pass(&self) -> &Arc<RenderPass> {
&self.render_pass
}
#[inline]
pub fn flags(&self) -> FramebufferCreateFlags {
self.flags
}
#[inline]
pub fn attachments(&self) -> &[Arc<ImageView>] {
DeviceOwnedDebugWrapper::cast_slice_inner(&self.attachments)
}
#[inline]
pub fn extent(&self) -> [u32; 2] {
self.extent
}
#[inline]
pub fn layers(&self) -> u32 {
self.layers
}
#[inline]
pub fn attached_layers_ranges(&self) -> SmallVec<[Range<u32>; 4]> {
self.attachments
.iter()
.map(|img| img.subresource_range().array_layers.clone())
.collect()
}
}
impl Drop for Framebuffer {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device().fns();
(fns.v1_0.destroy_framebuffer)(self.device().handle(), self.handle, ptr::null());
}
}
}
unsafe impl VulkanObject for Framebuffer {
type Handle = ash::vk::Framebuffer;
#[inline]
fn handle(&self) -> Self::Handle {
self.handle
}
}
unsafe impl DeviceOwned for Framebuffer {
#[inline]
fn device(&self) -> &Arc<Device> {
self.render_pass.device()
}
}
impl_id_counter!(Framebuffer);
#[derive(Clone, Debug)]
pub struct FramebufferCreateInfo {
pub flags: FramebufferCreateFlags,
pub attachments: Vec<Arc<ImageView>>,
pub extent: [u32; 2],
pub layers: u32,
pub _ne: crate::NonExhaustive,
}
impl Default for FramebufferCreateInfo {
#[inline]
fn default() -> Self {
Self {
flags: FramebufferCreateFlags::empty(),
attachments: Vec::new(),
extent: [0, 0],
layers: 0,
_ne: crate::NonExhaustive(()),
}
}
}
impl FramebufferCreateInfo {
fn set_auto_extent_layers(&mut self, render_pass: &RenderPass) {
let Self {
flags: _,
attachments,
extent,
layers,
_ne: _,
} = self;
let is_auto_extent = extent[0] == 0 || extent[1] == 0;
let is_auto_layers = *layers == 0;
if (is_auto_extent || is_auto_layers) && !attachments.is_empty() {
let mut auto_extent = [u32::MAX, u32::MAX];
let mut auto_layers = if render_pass.views_used() != 0 {
1
} else {
u32::MAX
};
for image_view in attachments.iter() {
let image_view_extent = image_view.image().extent();
let image_view_array_layers =
image_view.subresource_range().array_layers.len() as u32;
auto_extent[0] = auto_extent[0].min(image_view_extent[0]);
auto_extent[1] = auto_extent[1].min(image_view_extent[1]);
auto_layers = auto_layers.min(image_view_array_layers);
}
if is_auto_extent {
*extent = auto_extent;
}
if is_auto_layers {
*layers = auto_layers;
}
}
}
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
flags,
ref attachments,
extent,
layers,
_ne: _,
} = self;
flags.validate_device(device).map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-VkFramebufferCreateInfo-flags-parameter"])
})?;
for (index, image_view) in attachments.iter().enumerate() {
assert_eq!(device, image_view.device().as_ref());
let image_view_mip_levels = image_view.subresource_range().mip_levels.end
- image_view.subresource_range().mip_levels.start;
if image_view_mip_levels != 1 {
return Err(Box::new(ValidationError {
context: format!("attachments[{}]", index).into(),
problem: "has more than one mip level".into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-00883"],
..Default::default()
}));
}
if !image_view.component_mapping().is_identity() {
return Err(Box::new(ValidationError {
context: format!("attachments[{}]", index).into(),
problem: "is not identity swizzled".into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-00884"],
..Default::default()
}));
}
match image_view.view_type() {
ImageViewType::Dim2d | ImageViewType::Dim2dArray => {
if image_view.image().image_type() == ImageType::Dim3d
&& (image_view.format().aspects())
.intersects(ImageAspects::DEPTH | ImageAspects::STENCIL)
{
return Err(Box::new(ValidationError {
context: format!("attachments[{}]", index).into(),
problem: "is a 2D or 2D array image view, but its format is a \
depth/stencil format"
.into(),
vuids: &["VUID-VkFramebufferCreateInfo-pAttachments-00891"],
..Default::default()
}));
}
}
ImageViewType::Dim3d => {
return Err(Box::new(ValidationError {
context: format!("attachments[{}]", index).into(),
problem: "is a 3D image view".into(),
vuids: &["VUID-VkFramebufferCreateInfo-flags-04113"],
..Default::default()
}));
}
_ => (),
}
}
let properties = device.physical_device().properties();
if extent[0] == 0 {
return Err(Box::new(ValidationError {
context: "extent[0]".into(),
problem: "is zero".into(),
vuids: &["VUID-VkFramebufferCreateInfo-width-00885"],
..Default::default()
}));
}
if extent[0] > properties.max_framebuffer_width {
return Err(Box::new(ValidationError {
context: "extent[0]".into(),
problem: "exceeds the `max_framebuffer_width` limit".into(),
vuids: &["VUID-VkFramebufferCreateInfo-width-00886"],
..Default::default()
}));
}
if extent[1] == 0 {
return Err(Box::new(ValidationError {
context: "extent[1]".into(),
problem: "is zero".into(),
vuids: &["VUID-VkFramebufferCreateInfo-height-00887"],
..Default::default()
}));
}
if extent[1] > properties.max_framebuffer_height {
return Err(Box::new(ValidationError {
context: "extent[1]".into(),
problem: "exceeds the `max_framebuffer_height` limit".into(),
vuids: &["VUID-VkFramebufferCreateInfo-height-00888"],
..Default::default()
}));
}
if layers == 0 {
return Err(Box::new(ValidationError {
context: "layers".into(),
problem: "is zero".into(),
vuids: &["VUID-VkFramebufferCreateInfo-layers-00889"],
..Default::default()
}));
}
if layers > properties.max_framebuffer_layers {
return Err(Box::new(ValidationError {
context: "layers".into(),
problem: "exceeds the `max_framebuffer_layers` limit".into(),
vuids: &["VUID-VkFramebufferCreateInfo-layers-00890"],
..Default::default()
}));
}
Ok(())
}
}
vulkan_bitflags! {
#[non_exhaustive]
FramebufferCreateFlags = FramebufferCreateFlags(u32);
}
#[cfg(test)]
mod tests {
use crate::{
format::Format,
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage},
memory::allocator::{AllocationCreateInfo, StandardMemoryAllocator},
render_pass::{
Framebuffer, FramebufferCreateInfo, RenderPass, RenderPassCreateInfo,
SubpassDescription,
},
};
use std::sync::Arc;
#[test]
fn simple_create() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [color],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let view = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [1024, 768, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let _ = Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![view],
..Default::default()
},
)
.unwrap();
}
#[test]
fn check_device_limits() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = RenderPass::new(
device,
RenderPassCreateInfo {
subpasses: vec![SubpassDescription::default()],
..Default::default()
},
)
.unwrap();
assert!(Framebuffer::new(
render_pass.clone(),
FramebufferCreateInfo {
extent: [0xffffffff, 0xffffffff],
layers: 1,
..Default::default()
},
)
.is_err());
assert!(Framebuffer::new(
render_pass,
FramebufferCreateInfo {
extent: [1, 1],
layers: 0xffffffff,
..Default::default()
},
)
.is_err());
}
#[test]
fn attachment_format_mismatch() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [color],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let view = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8_UNORM,
extent: [1024, 768, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
assert!(Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![view],
..Default::default()
},
)
.is_err());
}
#[test]
fn attachment_dims_larger_than_specified_valid() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [color],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let view = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [600, 600, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let _ = Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![view],
extent: [512, 512],
layers: 1,
..Default::default()
},
)
.unwrap();
}
#[test]
fn attachment_dims_smaller_than_specified() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [color],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let view = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [512, 700, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
assert!(Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![view],
extent: [600, 600],
layers: 1,
..Default::default()
},
)
.is_err());
}
#[test]
fn multi_attachments_auto_smaller() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
a: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
b: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [a, b],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let a = ImageView::new_default(
Image::new(
memory_allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [256, 512, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let b = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [512, 128, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let framebuffer = Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![a, b],
..Default::default()
},
)
.unwrap();
match (framebuffer.extent(), framebuffer.layers()) {
([256, 128], 1) => (),
_ => panic!(),
}
}
#[test]
fn not_enough_attachments() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
a: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
b: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [a, b],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let view = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [256, 512, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
assert!(Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![view],
..Default::default()
},
)
.is_err());
}
#[test]
fn too_many_attachments() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = single_pass_renderpass!(
device.clone(),
attachments: {
a: {
format: Format::R8G8B8A8_UNORM,
samples: 1,
load_op: Clear,
store_op: DontCare,
},
},
pass: {
color: [a],
depth_stencil: {},
},
)
.unwrap();
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device));
let a = ImageView::new_default(
Image::new(
memory_allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [256, 512, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
let b = ImageView::new_default(
Image::new(
memory_allocator,
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_UNORM,
extent: [256, 512, 1],
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo::default(),
)
.unwrap(),
)
.unwrap();
assert!(Framebuffer::new(
render_pass,
FramebufferCreateInfo {
attachments: vec![a, b],
..Default::default()
},
)
.is_err());
}
#[test]
fn empty_working() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = RenderPass::new(
device,
RenderPassCreateInfo {
subpasses: vec![SubpassDescription::default()],
..Default::default()
},
)
.unwrap();
let _ = Framebuffer::new(
render_pass,
FramebufferCreateInfo {
extent: [512, 512],
layers: 1,
..Default::default()
},
)
.unwrap();
}
#[test]
fn cant_determine_dimensions_auto() {
let (device, _) = gfx_dev_and_queue!();
let render_pass = RenderPass::new(
device,
RenderPassCreateInfo {
subpasses: vec![SubpassDescription::default()],
..Default::default()
},
)
.unwrap();
assert!(Framebuffer::new(render_pass, FramebufferCreateInfo::default()).is_err());
}
}