//! Processing push constant ranges
//!
//! This module provides utitlity functions to make push constants root signature
//! compatible. Root constants are non-overlapping, therefore, the push constant
//! ranges passed at pipeline layout creation need to be `split` into disjunct
//! ranges. The disjunct ranges can be then converted into root signature entries.
use hal::pso;
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::ops::Range;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RootConstant {
pub stages: pso::ShaderStageFlags,
pub range: Range<u32>,
}
impl RootConstant {
fn is_empty(&self) -> bool {
self.range.end <= self.range.start
}
// Divide a root constant into two separate ranges depending on the overlap
// with another root constant.
fn divide(self, other: &RootConstant) -> (RootConstant, RootConstant) {
assert!(self.range.start <= other.range.start);
let left = RootConstant {
stages: self.stages,
range: self.range.start..other.range.start,
};
let right = RootConstant {
stages: self.stages,
range: other.range.start..self.range.end,
};
(left, right)
}
}
impl PartialOrd for RootConstant {
fn partial_cmp(&self, other: &RootConstant) -> Option<Ordering> {
Some(self.range.start.cmp(&other.range.start)
.then(self.range.end.cmp(&other.range.end))
.then(self.stages.cmp(&other.stages)))
}
}
impl Ord for RootConstant {
fn cmp(&self, other: &RootConstant) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
pub fn split<I>(ranges: I) -> Vec<RootConstant>
where
I: IntoIterator,
I::Item: Borrow<(pso::ShaderStageFlags, Range<u32>)>,
{
// Frontier of unexplored root constant ranges, sorted descending
// (less element shifting for Vec) regarding to the start of ranges.
let mut ranges = into_vec(ranges);
ranges.sort_by(|a, b| b.cmp(a));
// Storing resulting disjunct root constant ranges.
let mut disjunct = Vec::with_capacity(ranges.len());
while let Some(cur) = ranges.pop() {
// Run trough all unexplored ranges. After each run the frontier will be
// resorted!
//
// Case 1: Single element remaining
// Push is to the disjunct list, done.
// Case 2: At least two ranges, which possibly overlap
// Divide the first range into a left set and right set, depending
// on the overlap of the two ranges:
// Range 1: |---- left ---||--- right ---|
// Range 2: |--------...
if let Some(mut next) = ranges.pop() {
let (left, mut right) = cur.divide(&next);
if !left.is_empty() {
// The left part is, by definition, disjunct to all other ranges.
// Push all remaining pieces in the frontier, handled by the next
// iteration.
disjunct.push(left);
ranges.push(next);
if !right.is_empty() {
ranges.push(right);
}
} else if !right.is_empty() {
// If the left part is empty this means that both ranges have the
// same start value. The right segment is a candidate for a disjunct
// segment but we haven't checked against other ranges so far.
// Therefore, we push is on the frontier again, but added the
// stage flags from the overlapping segment.
// The second range will be shrunken to be disjunct with the pushed
// segment as we have already processed it.
// In the next iteration we will look again at the push right
// segment and compare it to other elements on the list until we
// have a small enough disjunct segment, which doesn't overlap
// with any part of the frontier.
right.stages |= next.stages;
next.range.start = right.range.end;
ranges.push(right);
if !next.is_empty() {
ranges.push(next);
}
}
} else {
disjunct.push(cur);
}
ranges.sort_by(|a, b| b.cmp(a));
}
disjunct
}
fn into_vec<I>(ranges: I) -> Vec<RootConstant>
where
I: IntoIterator,
I::Item: Borrow<(pso::ShaderStageFlags, Range<u32>)>,
{
ranges.into_iter().map(|borrowable| {
let &(stages, ref range) = borrowable.borrow();
RootConstant { stages, range: range.clone() }
}).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single() {
let range = &[(pso::ShaderStageFlags::VERTEX, 0..3)];
assert_eq!(into_vec(range), split(range));
}
#[test]
fn test_overlap_1() {
// Case:
// |----------|
// |------------|
let ranges = &[
(pso::ShaderStageFlags::VERTEX, 0..3),
(pso::ShaderStageFlags::FRAGMENT, 2..4),
];
let reference = vec![
RootConstant {
stages: pso::ShaderStageFlags::VERTEX,
range: 0..2,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::FRAGMENT,
range: 2..3,
},
RootConstant {
stages: pso::ShaderStageFlags::FRAGMENT,
range: 3..4,
},
];
assert_eq!(reference, split(ranges));
}
#[test]
fn test_overlap_2() {
// Case:
// |-------------------|
// |------------|
let ranges = &[
(pso::ShaderStageFlags::VERTEX, 0..5),
(pso::ShaderStageFlags::FRAGMENT, 2..4),
];
let reference = vec![
RootConstant {
stages: pso::ShaderStageFlags::VERTEX,
range: 0..2,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::FRAGMENT,
range: 2..4,
},
RootConstant {
stages: pso::ShaderStageFlags::VERTEX,
range: 4..5,
},
];
assert_eq!(reference, split(ranges));
}
#[test]
fn test_overlap_4() {
// Case:
// |--------------|
// |------------|
let ranges = &[
(pso::ShaderStageFlags::VERTEX, 0..5),
(pso::ShaderStageFlags::FRAGMENT, 0..4),
];
let reference = vec![
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::FRAGMENT,
range: 0..4,
},
RootConstant {
stages: pso::ShaderStageFlags::VERTEX,
range: 4..5,
},
];
assert_eq!(reference, split(ranges));
}
#[test]
fn test_equal() {
// Case:
// |-----|
// |-----|
let ranges = &[
(pso::ShaderStageFlags::VERTEX, 0..4),
(pso::ShaderStageFlags::FRAGMENT, 0..4),
];
let reference = vec![
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::FRAGMENT,
range: 0..4,
},
];
assert_eq!(reference, split(ranges));
}
#[test]
fn test_disjunct() {
// Case:
// |------|
// |------------|
let ranges = &[
(pso::ShaderStageFlags::VERTEX, 0..3),
(pso::ShaderStageFlags::FRAGMENT, 3..4),
];
assert_eq!(into_vec(ranges), split(ranges));
}
#[test]
fn test_complex() {
let ranges = &[
(pso::ShaderStageFlags::VERTEX, 2..10),
(pso::ShaderStageFlags::FRAGMENT, 0..5),
(pso::ShaderStageFlags::GEOMETRY, 6..10),
(pso::ShaderStageFlags::HULL, 4..7),
];
let reference = vec![
RootConstant {
stages: pso::ShaderStageFlags::FRAGMENT,
range: 0..2,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::FRAGMENT,
range: 2..4,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::FRAGMENT |
pso::ShaderStageFlags::HULL,
range: 4..5,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::HULL,
range: 5..6,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::GEOMETRY |
pso::ShaderStageFlags::HULL,
range: 6..7,
},
RootConstant {
stages:
pso::ShaderStageFlags::VERTEX |
pso::ShaderStageFlags::GEOMETRY,
range: 7..10,
},
];
assert_eq!(reference, split(ranges));
}
}