blob: 629dc8eebdb14c0d5d70ee8615a5574fea612c39 [file] [log] [blame]
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use crate::check_errors;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::format::FormatTy;
use crate::image::ImageLayout;
use crate::image::SampleCount;
use crate::pipeline::shader::ShaderInterface;
use crate::render_pass::AttachmentDesc;
use crate::render_pass::LoadOp;
use crate::render_pass::RenderPassDesc;
use crate::render_pass::SubpassDesc;
use crate::Error;
use crate::OomError;
use crate::VulkanObject;
use smallvec::SmallVec;
use std::error;
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;
/// An object representing the discrete steps in which rendering is done.
///
/// A render pass in Vulkan is made up of three parts:
/// - A list of attachments, which are image views that are inputs, outputs or intermediate stages
/// in the rendering process.
/// - One or more subpasses, which are the steps in which the rendering process, takes place,
/// and the attachments that are used for each step.
/// - Dependencies, which describe how the input and output data of each subpass is to be passed
/// from one subpass to the next.
///
/// In order to create a render pass, you must create a `RenderPassDesc` object that describes the
/// render pass, then pass it to `RenderPass::new`.
///
/// ```
/// use vulkano::render_pass::RenderPass;
/// use vulkano::render_pass::RenderPassDesc;
///
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// let desc = RenderPassDesc::empty();
/// let render_pass = RenderPass::new(device.clone(), desc).unwrap();
/// ```
///
/// This example creates a render pass with no attachment and one single subpass that doesn't draw
/// on anything. While it's sometimes useful, most of the time it's not what you want.
///
/// The easiest way to create a "real" render pass is to use the `single_pass_renderpass!` macro.
///
/// ```
/// # #[macro_use] extern crate vulkano;
/// # fn main() {
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// use vulkano::format::Format;
///
/// let render_pass = single_pass_renderpass!(device.clone(),
/// attachments: {
/// // `foo` is a custom name we give to the first and only attachment.
/// foo: {
/// load: Clear,
/// store: Store,
/// format: Format::R8G8B8A8Unorm,
/// samples: 1,
/// }
/// },
/// pass: {
/// color: [foo], // Repeat the attachment name here.
/// depth_stencil: {}
/// }
/// ).unwrap();
/// # }
/// ```
///
/// See the documentation of the macro for more details. TODO: put link here
pub struct RenderPass {
// The internal Vulkan object.
render_pass: ash::vk::RenderPass,
// Device this render pass was created from.
device: Arc<Device>,
// Description of the render pass.
desc: RenderPassDesc,
// Cache of the granularity of the render pass.
granularity: Mutex<Option<[u32; 2]>>,
}
impl RenderPass {
/// Builds a new render pass.
///
/// # Panic
///
/// - Can panic if it detects some violations in the restrictions. Only inexpensive checks are
/// performed. `debug_assert!` is used, so some restrictions are only checked in debug
/// mode.
///
pub fn new(
device: Arc<Device>,
description: RenderPassDesc,
) -> Result<RenderPass, RenderPassCreationError> {
let fns = device.fns();
// If the first use of an attachment in this render pass is as an input attachment, and
// the attachment is not also used as a color or depth/stencil attachment in the same
// subpass, then loadOp must not be VK_ATTACHMENT_LOAD_OP_CLEAR
debug_assert!(description.attachments().into_iter().enumerate().all(
|(atch_num, attachment)| {
if attachment.load != LoadOp::Clear {
return true;
}
for p in description.subpasses() {
if p.color_attachments
.iter()
.find(|&&(a, _)| a == atch_num)
.is_some()
{
return true;
}
if let Some((a, _)) = p.depth_stencil {
if a == atch_num {
return true;
}
}
if p.input_attachments
.iter()
.find(|&&(a, _)| a == atch_num)
.is_some()
{
return false;
}
}
true
}
));
let attachments = description
.attachments()
.iter()
.map(|attachment| {
ash::vk::AttachmentDescription {
flags: ash::vk::AttachmentDescriptionFlags::empty(), // FIXME: may alias flag
format: attachment.format.into(),
samples: attachment.samples.into(),
load_op: attachment.load.into(),
store_op: attachment.store.into(),
stencil_load_op: attachment.stencil_load.into(),
stencil_store_op: attachment.stencil_store.into(),
initial_layout: attachment.initial_layout.into(),
final_layout: attachment.final_layout.into(),
}
})
.collect::<SmallVec<[_; 16]>>();
// We need to pass pointers to vkAttachmentReference structs when creating the render pass.
// Therefore we need to allocate them in advance.
//
// This block allocates, for each pass, in order, all color attachment references, then all
// input attachment references, then all resolve attachment references, then the depth
// stencil attachment reference.
let attachment_references = description
.subpasses()
.iter()
.flat_map(|pass| {
// Performing some validation with debug asserts.
debug_assert!(
pass.resolve_attachments.is_empty()
|| pass.resolve_attachments.len() == pass.color_attachments.len()
);
debug_assert!(pass
.resolve_attachments
.iter()
.all(|a| attachments[a.0].samples == ash::vk::SampleCountFlags::TYPE_1));
debug_assert!(
pass.resolve_attachments.is_empty()
|| pass
.color_attachments
.iter()
.all(|a| attachments[a.0].samples.as_raw() > 1)
);
debug_assert!(
pass.resolve_attachments.is_empty()
|| pass
.resolve_attachments
.iter()
.zip(pass.color_attachments.iter())
.all(|(r, c)| { attachments[r.0].format == attachments[c.0].format })
);
debug_assert!(pass
.color_attachments
.iter()
.cloned()
.chain(pass.depth_stencil.clone().into_iter())
.chain(pass.input_attachments.iter().cloned())
.chain(pass.resolve_attachments.iter().cloned())
.all(|(a, _)| {
pass.preserve_attachments
.iter()
.find(|&&b| a == b)
.is_none()
}));
debug_assert!(pass
.color_attachments
.iter()
.cloned()
.chain(pass.depth_stencil.clone().into_iter())
.all(|(atch, layout)| {
if let Some(r) = pass.input_attachments.iter().find(|r| r.0 == atch) {
r.1 == layout
} else {
true
}
}));
let resolve = pass.resolve_attachments.iter().map(|&(offset, img_la)| {
debug_assert!(offset < attachments.len());
ash::vk::AttachmentReference {
attachment: offset as u32,
layout: img_la.into(),
}
});
let color = pass.color_attachments.iter().map(|&(offset, img_la)| {
debug_assert!(offset < attachments.len());
ash::vk::AttachmentReference {
attachment: offset as u32,
layout: img_la.into(),
}
});
let input = pass.input_attachments.iter().map(|&(offset, img_la)| {
debug_assert!(offset < attachments.len());
ash::vk::AttachmentReference {
attachment: offset as u32,
layout: img_la.into(),
}
});
let depthstencil = if let Some((offset, img_la)) = pass.depth_stencil {
Some(ash::vk::AttachmentReference {
attachment: offset as u32,
layout: img_la.into(),
})
} else {
None
}
.into_iter();
color.chain(input).chain(resolve).chain(depthstencil)
})
.collect::<SmallVec<[_; 16]>>();
// Same as `attachment_references` but only for the preserve attachments.
// This is separate because attachment references are u32s and not `vkAttachmentReference`
// structs.
let preserve_attachments_references = description
.subpasses()
.iter()
.flat_map(|pass| {
pass.preserve_attachments
.iter()
.map(|&offset| offset as u32)
})
.collect::<SmallVec<[_; 16]>>();
// Now iterating over passes.
let passes = unsafe {
// `ref_index` and `preserve_ref_index` are increased during the loop and point to the
// next element to use in respectively `attachment_references` and
// `preserve_attachments_references`.
let mut ref_index = 0usize;
let mut preserve_ref_index = 0usize;
let mut out: SmallVec<[_; 16]> = SmallVec::new();
for pass in description.subpasses() {
if pass.color_attachments.len() as u32
> device
.physical_device()
.properties()
.max_color_attachments
{
return Err(RenderPassCreationError::ColorAttachmentsLimitExceeded);
}
let color_attachments = attachment_references.as_ptr().offset(ref_index as isize);
ref_index += pass.color_attachments.len();
let input_attachments = attachment_references.as_ptr().offset(ref_index as isize);
ref_index += pass.input_attachments.len();
let resolve_attachments = attachment_references.as_ptr().offset(ref_index as isize);
ref_index += pass.resolve_attachments.len();
let depth_stencil = if pass.depth_stencil.is_some() {
let a = attachment_references.as_ptr().offset(ref_index as isize);
ref_index += 1;
a
} else {
ptr::null()
};
let preserve_attachments = preserve_attachments_references
.as_ptr()
.offset(preserve_ref_index as isize);
preserve_ref_index += pass.preserve_attachments.len();
out.push(ash::vk::SubpassDescription {
flags: ash::vk::SubpassDescriptionFlags::empty(),
pipeline_bind_point: ash::vk::PipelineBindPoint::GRAPHICS,
input_attachment_count: pass.input_attachments.len() as u32,
p_input_attachments: if pass.input_attachments.is_empty() {
ptr::null()
} else {
input_attachments
},
color_attachment_count: pass.color_attachments.len() as u32,
p_color_attachments: if pass.color_attachments.is_empty() {
ptr::null()
} else {
color_attachments
},
p_resolve_attachments: if pass.resolve_attachments.is_empty() {
ptr::null()
} else {
resolve_attachments
},
p_depth_stencil_attachment: depth_stencil,
preserve_attachment_count: pass.preserve_attachments.len() as u32,
p_preserve_attachments: if pass.preserve_attachments.is_empty() {
ptr::null()
} else {
preserve_attachments
},
});
}
assert!(!out.is_empty());
// If these assertions fails, there's a serious bug in the code above ^.
debug_assert!(ref_index == attachment_references.len());
debug_assert!(preserve_ref_index == preserve_attachments_references.len());
out
};
let dependencies = description
.dependencies()
.iter()
.map(|dependency| {
debug_assert!(
dependency.source_subpass as u32 == ash::vk::SUBPASS_EXTERNAL
|| dependency.source_subpass < passes.len()
);
debug_assert!(
dependency.destination_subpass as u32 == ash::vk::SUBPASS_EXTERNAL
|| dependency.destination_subpass < passes.len()
);
ash::vk::SubpassDependency {
src_subpass: dependency.source_subpass as u32,
dst_subpass: dependency.destination_subpass as u32,
src_stage_mask: dependency.source_stages.into(),
dst_stage_mask: dependency.destination_stages.into(),
src_access_mask: dependency.source_access.into(),
dst_access_mask: dependency.destination_access.into(),
dependency_flags: if dependency.by_region {
ash::vk::DependencyFlags::BY_REGION
} else {
ash::vk::DependencyFlags::empty()
},
}
})
.collect::<SmallVec<[_; 16]>>();
let multiview_create_info = match description.multiview() {
Some(multiview) => {
debug_assert!(device.enabled_features().multiview);
debug_assert!(
device
.physical_device()
.properties()
.max_multiview_view_count
.unwrap_or(0)
>= multiview.used_layer_count()
);
// each subpass must have a corresponding view mask
// or there are no view masks at all (which is probably a bug because
// nothing will get drawn)
debug_assert!(
multiview.view_masks.len() == passes.len() || multiview.view_masks.is_empty()
);
// either all subpasses must have a non-zero view mask or all must be zero
// (multiview is considered to be disabled when all view masks are zero)
debug_assert!(
multiview.view_masks.iter().all(|&mask| mask != 0)
|| multiview.view_masks.iter().all(|&mask| mask == 0)
);
// one view offset for each dependency
// or no view offsets at all
debug_assert!(
dependencies.len() == multiview.view_offsets.len()
|| multiview.view_offsets.is_empty()
);
// VUID-VkRenderPassCreateInfo-pNext-02512
debug_assert!(dependencies.iter().zip(&multiview.view_offsets).all(
|(dependency, &view_offset)| dependency
.dependency_flags
.contains(ash::vk::DependencyFlags::VIEW_LOCAL)
|| view_offset == 0
));
// VUID-VkRenderPassCreateInfo-pNext-02514
debug_assert!(
multiview.view_masks.iter().any(|&view_mask| view_mask != 0)
|| dependencies.iter().all(|dependency| !dependency
.dependency_flags
.contains(ash::vk::DependencyFlags::VIEW_LOCAL))
);
// VUID-VkRenderPassCreateInfo-pNext-02515
debug_assert!(
multiview.view_masks.iter().any(|&view_mask| view_mask != 0)
|| multiview.correlation_masks.is_empty()
);
// VUID-VkRenderPassMultiviewCreateInfo-pCorrelationMasks-00841
// ensure that each view index is contained in at most one correlation mask
// by checking for any overlap in all pairs of correlation masks
debug_assert!(multiview
.correlation_masks
.iter()
.enumerate()
.all(|(i, &mask)| multiview.correlation_masks[i + 1..]
.iter()
.all(|&other_mask| other_mask & mask == 0)));
ash::vk::RenderPassMultiviewCreateInfo {
subpass_count: passes.len() as u32,
p_view_masks: multiview.view_masks.as_ptr(),
dependency_count: dependencies.len() as u32,
p_view_offsets: multiview.view_offsets.as_ptr(),
correlation_mask_count: multiview.correlation_masks.len() as u32,
p_correlation_masks: multiview.correlation_masks.as_ptr(),
..Default::default()
}
}
None => ash::vk::RenderPassMultiviewCreateInfo::default(),
};
let render_pass = unsafe {
let infos = ash::vk::RenderPassCreateInfo {
p_next: if description.multiview().is_none() {
ptr::null()
} else {
&multiview_create_info as *const _ as _
},
flags: ash::vk::RenderPassCreateFlags::empty(),
attachment_count: attachments.len() as u32,
p_attachments: if attachments.is_empty() {
ptr::null()
} else {
attachments.as_ptr()
},
subpass_count: passes.len() as u32,
p_subpasses: if passes.is_empty() {
ptr::null()
} else {
passes.as_ptr()
},
dependency_count: dependencies.len() as u32,
p_dependencies: if dependencies.is_empty() {
ptr::null()
} else {
dependencies.as_ptr()
},
..Default::default()
};
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.create_render_pass(
device.internal_object(),
&infos,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(RenderPass {
device: device.clone(),
render_pass,
desc: description,
granularity: Mutex::new(None),
})
}
/// Builds a render pass with one subpass and no attachment.
///
/// This method is useful for quick tests.
#[inline]
pub fn empty_single_pass(device: Arc<Device>) -> Result<RenderPass, RenderPassCreationError> {
RenderPass::new(device, RenderPassDesc::empty())
}
#[inline]
pub fn inner(&self) -> RenderPassSys {
RenderPassSys(self.render_pass, PhantomData)
}
/// Returns the granularity of this render pass.
///
/// If the render area of a render pass in a command buffer is a multiple of this granularity,
/// then the performance will be optimal. Performances are always optimal for render areas
/// that cover the whole framebuffer.
pub fn granularity(&self) -> [u32; 2] {
let mut granularity = self.granularity.lock().unwrap();
if let Some(&granularity) = granularity.as_ref() {
return granularity;
}
unsafe {
let fns = self.device.fns();
let mut out = MaybeUninit::uninit();
fns.v1_0.get_render_area_granularity(
self.device.internal_object(),
self.render_pass,
out.as_mut_ptr(),
);
let out = out.assume_init();
debug_assert_ne!(out.width, 0);
debug_assert_ne!(out.height, 0);
let gran = [out.width, out.height];
*granularity = Some(gran);
gran
}
}
/// Returns the description of the render pass.
#[inline]
pub fn desc(&self) -> &RenderPassDesc {
&self.desc
}
}
unsafe impl DeviceOwned for RenderPass {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl fmt::Debug for RenderPass {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.debug_struct("RenderPass")
.field("raw", &self.render_pass)
.field("device", &self.device)
.field("desc", &self.desc)
.finish()
}
}
impl Drop for RenderPass {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
fns.v1_0.destroy_render_pass(
self.device.internal_object(),
self.render_pass,
ptr::null(),
);
}
}
}
/// Opaque object that represents the render pass' internals.
#[derive(Debug, Copy, Clone)]
pub struct RenderPassSys<'a>(ash::vk::RenderPass, PhantomData<&'a ()>);
unsafe impl<'a> VulkanObject for RenderPassSys<'a> {
type Object = ash::vk::RenderPass;
#[inline]
fn internal_object(&self) -> ash::vk::RenderPass {
self.0
}
}
/// Error that can happen when creating a compute pipeline.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RenderPassCreationError {
/// Not enough memory.
OomError(OomError),
/// The maximum number of color attachments has been exceeded.
ColorAttachmentsLimitExceeded,
}
impl error::Error for RenderPassCreationError {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
RenderPassCreationError::OomError(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for RenderPassCreationError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
fmt,
"{}",
match *self {
RenderPassCreationError::OomError(_) => "not enough memory available",
RenderPassCreationError::ColorAttachmentsLimitExceeded => {
"the maximum number of color attachments has been exceeded"
}
}
)
}
}
impl From<OomError> for RenderPassCreationError {
#[inline]
fn from(err: OomError) -> RenderPassCreationError {
RenderPassCreationError::OomError(err)
}
}
impl From<Error> for RenderPassCreationError {
#[inline]
fn from(err: Error) -> RenderPassCreationError {
match err {
err @ Error::OutOfHostMemory => RenderPassCreationError::OomError(OomError::from(err)),
err @ Error::OutOfDeviceMemory => {
RenderPassCreationError::OomError(OomError::from(err))
}
_ => panic!("unexpected error: {:?}", err),
}
}
}
/// Represents a subpass within a `RenderPass` object.
///
/// This struct doesn't correspond to anything in Vulkan. It is simply an equivalent to a
/// tuple of a render pass and subpass index. Contrary to a tuple, however, the existence of the
/// subpass is checked when the object is created. When you have a `Subpass` you are guaranteed
/// that the given subpass does exist.
#[derive(Debug, Clone)]
pub struct Subpass {
render_pass: Arc<RenderPass>,
subpass_id: u32,
}
impl Subpass {
/// Returns a handle that represents a subpass of a render pass.
#[inline]
pub fn from(render_pass: Arc<RenderPass>, id: u32) -> Option<Subpass> {
if (id as usize) < render_pass.desc().subpasses().len() {
Some(Subpass {
render_pass,
subpass_id: id,
})
} else {
None
}
}
#[inline]
fn subpass_desc(&self) -> &SubpassDesc {
&self.render_pass.desc().subpasses()[self.subpass_id as usize]
}
#[inline]
fn attachment_desc(&self, atch_num: usize) -> &AttachmentDesc {
&self.render_pass.desc().attachments()[atch_num]
}
/// Returns the number of color attachments in this subpass.
#[inline]
pub fn num_color_attachments(&self) -> u32 {
self.subpass_desc().color_attachments.len() as u32
}
/// Returns true if the subpass has a depth attachment or a depth-stencil attachment.
#[inline]
pub fn has_depth(&self) -> bool {
let subpass_desc = self.subpass_desc();
let atch_num = match subpass_desc.depth_stencil {
Some((d, _)) => d,
None => return false,
};
match self.attachment_desc(atch_num).format.ty() {
FormatTy::Depth => true,
FormatTy::Stencil => false,
FormatTy::DepthStencil => true,
_ => unreachable!(),
}
}
/// Returns true if the subpass has a depth attachment or a depth-stencil attachment whose
/// layout is not `DepthStencilReadOnlyOptimal`.
#[inline]
pub fn has_writable_depth(&self) -> bool {
let subpass_desc = self.subpass_desc();
let atch_num = match subpass_desc.depth_stencil {
Some((d, l)) => {
if l == ImageLayout::DepthStencilReadOnlyOptimal {
return false;
}
d
}
None => return false,
};
match self.attachment_desc(atch_num).format.ty() {
FormatTy::Depth => true,
FormatTy::Stencil => false,
FormatTy::DepthStencil => true,
_ => unreachable!(),
}
}
/// Returns true if the subpass has a stencil attachment or a depth-stencil attachment.
#[inline]
pub fn has_stencil(&self) -> bool {
let subpass_desc = self.subpass_desc();
let atch_num = match subpass_desc.depth_stencil {
Some((d, _)) => d,
None => return false,
};
match self.attachment_desc(atch_num).format.ty() {
FormatTy::Depth => false,
FormatTy::Stencil => true,
FormatTy::DepthStencil => true,
_ => unreachable!(),
}
}
/// Returns true if the subpass has a stencil attachment or a depth-stencil attachment whose
/// layout is not `DepthStencilReadOnlyOptimal`.
#[inline]
pub fn has_writable_stencil(&self) -> bool {
let subpass_desc = self.subpass_desc();
let atch_num = match subpass_desc.depth_stencil {
Some((d, l)) => {
if l == ImageLayout::DepthStencilReadOnlyOptimal {
return false;
}
d
}
None => return false,
};
match self.attachment_desc(atch_num).format.ty() {
FormatTy::Depth => false,
FormatTy::Stencil => true,
FormatTy::DepthStencil => true,
_ => unreachable!(),
}
}
/// Returns true if the subpass has any color or depth/stencil attachment.
#[inline]
pub fn has_color_or_depth_stencil_attachment(&self) -> bool {
if self.num_color_attachments() >= 1 {
return true;
}
let subpass_desc = self.subpass_desc();
match subpass_desc.depth_stencil {
Some((d, _)) => true,
None => false,
}
}
/// Returns the number of samples in the color and/or depth/stencil attachments. Returns `None`
/// if there is no such attachment in this subpass.
#[inline]
pub fn num_samples(&self) -> Option<SampleCount> {
let subpass_desc = self.subpass_desc();
// TODO: chain input attachments as well?
subpass_desc
.color_attachments
.iter()
.cloned()
.chain(subpass_desc.depth_stencil.clone().into_iter())
.filter_map(|a| self.render_pass.desc().attachments().get(a.0))
.next()
.map(|a| a.samples)
}
/// Returns the render pass of this subpass.
#[inline]
pub fn render_pass(&self) -> &Arc<RenderPass> {
&self.render_pass
}
/// Returns the index of this subpass within the renderpass.
#[inline]
pub fn index(&self) -> u32 {
self.subpass_id
}
/// Returns `true` if this subpass is compatible with the fragment output definition.
// TODO: return proper error
pub fn is_compatible_with(&self, shader_interface: &ShaderInterface) -> bool {
self.render_pass
.desc()
.is_compatible_with_shader(self.subpass_id, shader_interface)
}
}
impl From<Subpass> for (Arc<RenderPass>, u32) {
#[inline]
fn from(value: Subpass) -> (Arc<RenderPass>, u32) {
(value.render_pass, value.subpass_id)
}
}
#[cfg(test)]
mod tests {
use crate::format::Format;
use crate::render_pass::RenderPass;
use crate::render_pass::RenderPassCreationError;
#[test]
fn empty() {
let (device, _) = gfx_dev_and_queue!();
let _ = RenderPass::empty_single_pass(device).unwrap();
}
#[test]
fn too_many_color_atch() {
let (device, _) = gfx_dev_and_queue!();
if device
.physical_device()
.properties()
.max_color_attachments
>= 10
{
return; // test ignored
}
let rp = single_pass_renderpass! {
device.clone(),
attachments: {
a1: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a2: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a3: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a4: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a5: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a6: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a7: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a8: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a9: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, },
a10: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, }
},
pass: {
color: [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10],
depth_stencil: {}
}
};
match rp {
Err(RenderPassCreationError::ColorAttachmentsLimitExceeded) => (),
_ => panic!(),
}
}
#[test]
fn non_zero_granularity() {
let (device, _) = gfx_dev_and_queue!();
let rp = single_pass_renderpass! {
device.clone(),
attachments: {
a: { load: Clear, store: DontCare, format: Format::R8G8B8A8Unorm, samples: 1, }
},
pass: {
color: [a],
depth_stencil: {}
}
}
.unwrap();
let granularity = rp.granularity();
assert_ne!(granularity[0], 0);
assert_ne!(granularity[1], 0);
}
}