aboutsummaryrefslogtreecommitdiff
path: root/src/pipeline/layout/sys.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/pipeline/layout/sys.rs')
-rw-r--r--src/pipeline/layout/sys.rs469
1 files changed, 469 insertions, 0 deletions
diff --git a/src/pipeline/layout/sys.rs b/src/pipeline/layout/sys.rs
new file mode 100644
index 0000000..7d6d33b
--- /dev/null
+++ b/src/pipeline/layout/sys.rs
@@ -0,0 +1,469 @@
+// 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 super::limits_check;
+use crate::check_errors;
+use crate::descriptor_set::layout::DescriptorSetDesc;
+use crate::descriptor_set::layout::DescriptorSetDescSupersetError;
+use crate::descriptor_set::layout::DescriptorSetLayout;
+use crate::device::Device;
+use crate::device::DeviceOwned;
+use crate::pipeline::layout::PipelineLayoutLimitsError;
+use crate::pipeline::shader::ShaderStages;
+use crate::Error;
+use crate::OomError;
+use crate::VulkanObject;
+use smallvec::SmallVec;
+use std::cmp;
+use std::error;
+use std::fmt;
+use std::mem::MaybeUninit;
+use std::ptr;
+use std::sync::Arc;
+
+/// Wrapper around the `PipelineLayout` Vulkan object. Describes to the Vulkan implementation the
+/// descriptor sets and push constants available to your shaders.
+pub struct PipelineLayout {
+ handle: ash::vk::PipelineLayout,
+ device: Arc<Device>,
+ descriptor_set_layouts: SmallVec<[Arc<DescriptorSetLayout>; 16]>,
+ push_constant_ranges: SmallVec<[PipelineLayoutPcRange; 8]>,
+}
+
+impl PipelineLayout {
+ /// Creates a new `PipelineLayout`.
+ #[inline]
+ pub fn new<D, P>(
+ device: Arc<Device>,
+ descriptor_set_layouts: D,
+ push_constant_ranges: P,
+ ) -> Result<PipelineLayout, PipelineLayoutCreationError>
+ where
+ D: IntoIterator<Item = Arc<DescriptorSetLayout>>,
+ P: IntoIterator<Item = PipelineLayoutPcRange>,
+ {
+ let fns = device.fns();
+ let descriptor_set_layouts: SmallVec<[Arc<DescriptorSetLayout>; 16]> =
+ descriptor_set_layouts.into_iter().collect();
+ let push_constant_ranges: SmallVec<[PipelineLayoutPcRange; 8]> =
+ push_constant_ranges.into_iter().collect();
+
+ // Check for overlapping stages
+ for (a_id, a) in push_constant_ranges.iter().enumerate() {
+ for b in push_constant_ranges.iter().skip(a_id + 1) {
+ if a.stages.intersects(&b.stages) {
+ return Err(PipelineLayoutCreationError::PushConstantsConflict {
+ first_range: *a,
+ second_range: *b,
+ });
+ }
+ }
+ }
+
+ // Check against device limits
+ limits_check::check_desc_against_limits(
+ device.physical_device().properties(),
+ &descriptor_set_layouts,
+ &push_constant_ranges,
+ )?;
+
+ // Grab the list of `vkDescriptorSetLayout` objects from `layouts`.
+ let layouts_ids = descriptor_set_layouts
+ .iter()
+ .map(|l| l.internal_object())
+ .collect::<SmallVec<[_; 16]>>();
+
+ // Builds a list of `vkPushConstantRange` that describe the push constants.
+ let push_constants = {
+ let mut out: SmallVec<[_; 8]> = SmallVec::new();
+
+ for &PipelineLayoutPcRange {
+ offset,
+ size,
+ stages,
+ } in &push_constant_ranges
+ {
+ if stages == ShaderStages::none() || size == 0 || (size % 4) != 0 {
+ return Err(PipelineLayoutCreationError::InvalidPushConstant);
+ }
+
+ out.push(ash::vk::PushConstantRange {
+ stage_flags: stages.into(),
+ offset: offset as u32,
+ size: size as u32,
+ });
+ }
+
+ out
+ };
+
+ // Each bit of `stageFlags` must only be present in a single push constants range.
+ // We check that with a debug_assert because it's supposed to be enforced by the
+ // `PipelineLayoutDesc`.
+ debug_assert!({
+ let mut stages = ash::vk::ShaderStageFlags::empty();
+ let mut outcome = true;
+ for pc in push_constants.iter() {
+ if !(stages & pc.stage_flags).is_empty() {
+ outcome = false;
+ break;
+ }
+ stages &= pc.stage_flags;
+ }
+ outcome
+ });
+
+ // FIXME: it is not legal to pass eg. the TESSELLATION_SHADER bit when the device doesn't
+ // have tess shaders enabled
+
+ // Build the final object.
+ let handle = unsafe {
+ let infos = ash::vk::PipelineLayoutCreateInfo {
+ flags: ash::vk::PipelineLayoutCreateFlags::empty(),
+ set_layout_count: layouts_ids.len() as u32,
+ p_set_layouts: layouts_ids.as_ptr(),
+ push_constant_range_count: push_constants.len() as u32,
+ p_push_constant_ranges: push_constants.as_ptr(),
+ ..Default::default()
+ };
+
+ let mut output = MaybeUninit::uninit();
+ check_errors(fns.v1_0.create_pipeline_layout(
+ device.internal_object(),
+ &infos,
+ ptr::null(),
+ output.as_mut_ptr(),
+ ))?;
+ output.assume_init()
+ };
+
+ Ok(PipelineLayout {
+ handle,
+ device: device.clone(),
+ descriptor_set_layouts,
+ push_constant_ranges,
+ })
+ }
+}
+
+impl PipelineLayout {
+ /// Returns the descriptor set layouts this pipeline layout was created from.
+ #[inline]
+ pub fn descriptor_set_layouts(&self) -> &[Arc<DescriptorSetLayout>] {
+ &self.descriptor_set_layouts
+ }
+
+ /// Returns a slice containing the push constant ranges this pipeline layout was created from.
+ #[inline]
+ pub fn push_constant_ranges(&self) -> &[PipelineLayoutPcRange] {
+ &self.push_constant_ranges
+ }
+
+ /// Makes sure that `self` is a superset of the provided descriptor set layouts and push
+ /// constant ranges. Returns an `Err` if this is not the case.
+ pub fn ensure_superset_of(
+ &self,
+ descriptor_set_layout_descs: &[DescriptorSetDesc],
+ push_constant_range: &Option<PipelineLayoutPcRange>,
+ ) -> Result<(), PipelineLayoutSupersetError> {
+ // Ewwwwwww
+ let empty = DescriptorSetDesc::empty();
+ let num_sets = cmp::max(
+ self.descriptor_set_layouts.len(),
+ descriptor_set_layout_descs.len(),
+ );
+
+ for set_num in 0..num_sets {
+ let first = self
+ .descriptor_set_layouts
+ .get(set_num)
+ .map(|set| set.desc())
+ .unwrap_or_else(|| &empty);
+ let second = descriptor_set_layout_descs
+ .get(set_num)
+ .unwrap_or_else(|| &empty);
+
+ if let Err(error) = first.ensure_superset_of(second) {
+ return Err(PipelineLayoutSupersetError::DescriptorSet {
+ error,
+ set_num: set_num as u32,
+ });
+ }
+ }
+
+ // FIXME: check push constants
+ if let Some(range) = push_constant_range {
+ for own_range in self.push_constant_ranges.as_ref().into_iter() {
+ if range.stages.intersects(&own_range.stages) && // check if it shares any stages
+ (range.offset < own_range.offset || // our range must start before and end after the given range
+ own_range.offset + own_range.size < range.offset + range.size) {
+ return Err(PipelineLayoutSupersetError::PushConstantRange {
+ first_range: *own_range,
+ second_range: *range,
+ });
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+unsafe impl DeviceOwned for PipelineLayout {
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ &self.device
+ }
+}
+
+unsafe impl VulkanObject for PipelineLayout {
+ type Object = ash::vk::PipelineLayout;
+
+ fn internal_object(&self) -> Self::Object {
+ self.handle
+ }
+}
+
+impl fmt::Debug for PipelineLayout {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ fmt.debug_struct("PipelineLayout")
+ .field("raw", &self.handle)
+ .field("device", &self.device)
+ .field("descriptor_set_layouts", &self.descriptor_set_layouts)
+ .field("push_constant_ranges", &self.push_constant_ranges)
+ .finish()
+ }
+}
+
+impl Drop for PipelineLayout {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ let fns = self.device.fns();
+ fns.v1_0.destroy_pipeline_layout(
+ self.device.internal_object(),
+ self.handle,
+ ptr::null(),
+ );
+ }
+ }
+}
+
+/// Error that can happen when creating a pipeline layout.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum PipelineLayoutCreationError {
+ /// Not enough memory.
+ OomError(OomError),
+ /// The pipeline layout description doesn't fulfill the limit requirements.
+ LimitsError(PipelineLayoutLimitsError),
+ /// One of the push constants range didn't obey the rules. The list of stages must not be
+ /// empty, the size must not be 0, and the size must be a multiple or 4.
+ InvalidPushConstant,
+ /// Conflict between different push constants ranges.
+ PushConstantsConflict {
+ first_range: PipelineLayoutPcRange,
+ second_range: PipelineLayoutPcRange,
+ },
+}
+
+impl error::Error for PipelineLayoutCreationError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match *self {
+ PipelineLayoutCreationError::OomError(ref err) => Some(err),
+ PipelineLayoutCreationError::LimitsError(ref err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+impl fmt::Display for PipelineLayoutCreationError {
+ #[inline]
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ write!(
+ fmt,
+ "{}",
+ match *self {
+ PipelineLayoutCreationError::OomError(_) => "not enough memory available",
+ PipelineLayoutCreationError::LimitsError(_) => {
+ "the pipeline layout description doesn't fulfill the limit requirements"
+ }
+ PipelineLayoutCreationError::InvalidPushConstant => {
+ "one of the push constants range didn't obey the rules"
+ }
+ PipelineLayoutCreationError::PushConstantsConflict { .. } => {
+ "conflict between different push constants ranges"
+ }
+ }
+ )
+ }
+}
+
+impl From<OomError> for PipelineLayoutCreationError {
+ #[inline]
+ fn from(err: OomError) -> PipelineLayoutCreationError {
+ PipelineLayoutCreationError::OomError(err)
+ }
+}
+
+impl From<PipelineLayoutLimitsError> for PipelineLayoutCreationError {
+ #[inline]
+ fn from(err: PipelineLayoutLimitsError) -> PipelineLayoutCreationError {
+ PipelineLayoutCreationError::LimitsError(err)
+ }
+}
+
+impl From<Error> for PipelineLayoutCreationError {
+ #[inline]
+ fn from(err: Error) -> PipelineLayoutCreationError {
+ match err {
+ err @ Error::OutOfHostMemory => {
+ PipelineLayoutCreationError::OomError(OomError::from(err))
+ }
+ err @ Error::OutOfDeviceMemory => {
+ PipelineLayoutCreationError::OomError(OomError::from(err))
+ }
+ _ => panic!("unexpected error: {:?}", err),
+ }
+ }
+}
+
+/// Error when checking whether a pipeline layout is a superset of another one.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum PipelineLayoutSupersetError {
+ DescriptorSet {
+ error: DescriptorSetDescSupersetError,
+ set_num: u32,
+ },
+ PushConstantRange {
+ first_range: PipelineLayoutPcRange,
+ second_range: PipelineLayoutPcRange,
+ },
+}
+
+impl error::Error for PipelineLayoutSupersetError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match *self {
+ PipelineLayoutSupersetError::DescriptorSet { ref error, .. } => Some(error),
+ ref error @ PipelineLayoutSupersetError::PushConstantRange { .. } => Some(error),
+ }
+ }
+}
+
+impl fmt::Display for PipelineLayoutSupersetError {
+ #[inline]
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match *self {
+ PipelineLayoutSupersetError::DescriptorSet { .. } => {
+ write!(
+ fmt,
+ "the descriptor set was not a superset of the other"
+ )
+ },
+ PipelineLayoutSupersetError::PushConstantRange { first_range, second_range } => {
+ writeln!(fmt, "our range did not completely encompass the other range")?;
+ writeln!(fmt, " our stages: {:?}", first_range.stages)?;
+ writeln!(
+ fmt,
+ " our range: {} - {}",
+ first_range.offset,
+ first_range.offset + first_range.size
+ )?;
+ writeln!(fmt, " other stages: {:?}", second_range.stages)?;
+ write!(fmt,
+ " other range: {} - {}",
+ second_range.offset,
+ second_range.offset + second_range.size
+ )
+ },
+ }
+ }
+}
+
+/// Description of a range of the push constants of a pipeline layout.
+// TODO: should contain the layout as well
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct PipelineLayoutPcRange {
+ /// Offset in bytes from the start of the push constants to this range.
+ pub offset: usize,
+ /// Size in bytes of the range.
+ pub size: usize,
+ /// The stages which can access this range. Note that the same shader stage can't access two
+ /// different ranges.
+ pub stages: ShaderStages,
+}
+
+/* TODO: restore
+#[cfg(test)]
+mod tests {
+ use std::iter;
+ use std::sync::Arc;
+ use descriptor::descriptor::ShaderStages;
+ use descriptor::descriptor_set::DescriptorSetLayout;
+ use descriptor::pipeline_layout::sys::PipelineLayout;
+ use descriptor::pipeline_layout::sys::PipelineLayoutCreationError;
+
+ #[test]
+ fn empty() {
+ let (device, _) = gfx_dev_and_queue!();
+ let _layout = PipelineLayout::new(&device, iter::empty(), iter::empty()).unwrap();
+ }
+
+ #[test]
+ fn wrong_device_panic() {
+ let (device1, _) = gfx_dev_and_queue!();
+ let (device2, _) = gfx_dev_and_queue!();
+
+ let set = match DescriptorSetLayout::raw(device1, iter::empty()) {
+ Ok(s) => Arc::new(s),
+ Err(_) => return
+ };
+
+ assert_should_panic!({
+ let _ = PipelineLayout::new(&device2, Some(&set), iter::empty());
+ });
+ }
+
+ #[test]
+ fn invalid_push_constant_stages() {
+ let (device, _) = gfx_dev_and_queue!();
+
+ let push_constant = (0, 8, ShaderStages::none());
+
+ match PipelineLayout::new(&device, iter::empty(), Some(push_constant)) {
+ Err(PipelineLayoutCreationError::InvalidPushConstant) => (),
+ _ => panic!()
+ }
+ }
+
+ #[test]
+ fn invalid_push_constant_size1() {
+ let (device, _) = gfx_dev_and_queue!();
+
+ let push_constant = (0, 0, ShaderStages::all_graphics());
+
+ match PipelineLayout::new(&device, iter::empty(), Some(push_constant)) {
+ Err(PipelineLayoutCreationError::InvalidPushConstant) => (),
+ _ => panic!()
+ }
+ }
+
+ #[test]
+ fn invalid_push_constant_size2() {
+ let (device, _) = gfx_dev_and_queue!();
+
+ let push_constant = (0, 11, ShaderStages::all_graphics());
+
+ match PipelineLayout::new(&device, iter::empty(), Some(push_constant)) {
+ Err(PipelineLayoutCreationError::InvalidPushConstant) => (),
+ _ => panic!()
+ }
+ }
+}*/