refactor: simplify barrier building logic and remove unused structures
This commit is contained in:
parent
ac1b6c9cc8
commit
d896a207de
|
|
@ -19,8 +19,7 @@ use crate::{
|
||||||
recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range},
|
recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range},
|
||||||
resources::{
|
resources::{
|
||||||
Buffer, GraphBufferDesc, GraphTextureDesc, ResourceDesc, ResourceId, Subresource,
|
Buffer, GraphBufferDesc, GraphTextureDesc, ResourceDesc, ResourceId, Subresource,
|
||||||
SubresourceScope, SubresourceScopeInner, Texture, access_flag_is_read,
|
SubresourceScope, SubresourceScopeInner, Texture, access_flag_is_write,
|
||||||
access_flag_is_write,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -55,40 +54,6 @@ pub struct PipelineBarrier {
|
||||||
pub resources: Vec<BarrierResource>,
|
pub resources: Vec<BarrierResource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
struct AccessRef {
|
|
||||||
ordered_index: usize,
|
|
||||||
access_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
struct BarrierResourceRef {
|
|
||||||
barrier_index: usize,
|
|
||||||
resource_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BarrierResourceRef {
|
|
||||||
fn get(self, barriers: &[PipelineBarrier]) -> &BarrierResource {
|
|
||||||
&barriers[self.barrier_index].resources[self.resource_index]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn widen(
|
|
||||||
self,
|
|
||||||
barriers: &mut [PipelineBarrier],
|
|
||||||
stage: vk::PipelineStageFlags2,
|
|
||||||
access: vk::AccessFlags2,
|
|
||||||
) {
|
|
||||||
let resource = &mut barriers[self.barrier_index].resources[self.resource_index];
|
|
||||||
resource.dst_stage |= stage;
|
|
||||||
resource.dst_access |= access;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmittedBarrierEntry {
|
|
||||||
access: AccessRef,
|
|
||||||
resource: BarrierResourceRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct ResourceLifetime {
|
pub struct ResourceLifetime {
|
||||||
pub first_command: CommandIndex,
|
pub first_command: CommandIndex,
|
||||||
|
|
@ -289,8 +254,7 @@ impl RenderGraph {
|
||||||
let compiler = GraphCompiler::new(&self.resources, accesses, command_count);
|
let compiler = GraphCompiler::new(&self.resources, accesses, command_count);
|
||||||
let batches = compiler.topo_batches()?;
|
let batches = compiler.topo_batches()?;
|
||||||
compiler.validate_resources()?;
|
compiler.validate_resources()?;
|
||||||
let flat_order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
let barriers = compiler.build_barriers(&batches);
|
||||||
let barriers = compiler.build_barriers(&flat_order, &batches);
|
|
||||||
let (lifetimes, interference) = compiler.bind_lifetimes();
|
let (lifetimes, interference) = compiler.bind_lifetimes();
|
||||||
|
|
||||||
Ok(CompiledGraph {
|
Ok(CompiledGraph {
|
||||||
|
|
@ -298,7 +262,6 @@ impl RenderGraph {
|
||||||
resolved: HashMap::new(),
|
resolved: HashMap::new(),
|
||||||
commands: self.commands,
|
commands: self.commands,
|
||||||
batches,
|
batches,
|
||||||
flat_order,
|
|
||||||
barriers,
|
barriers,
|
||||||
lifetimes,
|
lifetimes,
|
||||||
interference,
|
interference,
|
||||||
|
|
@ -372,22 +335,17 @@ impl<'a> GraphCompiler<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_barriers(
|
fn build_barriers(&self, batches: &[Vec<CommandIndex>]) -> Vec<PipelineBarrier> {
|
||||||
&self,
|
|
||||||
order: &[CommandIndex],
|
|
||||||
batches: &[Vec<CommandIndex>],
|
|
||||||
) -> Vec<PipelineBarrier> {
|
|
||||||
let initial_states = self
|
let initial_states = self
|
||||||
.resources
|
.resources
|
||||||
.iter()
|
.iter()
|
||||||
.map(ResourceDesc::initial_state)
|
.map(ResourceDesc::initial_state)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.build_barriers_with_initial_states(order, batches, &initial_states)
|
self.build_barriers_with_initial_states(batches, &initial_states)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_barriers_with_initial_states(
|
fn build_barriers_with_initial_states(
|
||||||
&self,
|
&self,
|
||||||
order: &[CommandIndex],
|
|
||||||
batches: &[Vec<CommandIndex>],
|
batches: &[Vec<CommandIndex>],
|
||||||
initial_states: &[crate::resources::ResourceState],
|
initial_states: &[crate::resources::ResourceState],
|
||||||
) -> Vec<PipelineBarrier> {
|
) -> Vec<PipelineBarrier> {
|
||||||
|
|
@ -400,136 +358,27 @@ impl<'a> GraphCompiler<'a> {
|
||||||
|
|
||||||
let mut result: Vec<PipelineBarrier> = Vec::new();
|
let mut result: Vec<PipelineBarrier> = Vec::new();
|
||||||
|
|
||||||
// Associates each access that produced a barrier with its
|
for (ordered_index, &command_index) in batches.iter().flatten().enumerate() {
|
||||||
// BarrierResource, so a later contained read can widen its dst masks.
|
|
||||||
// This empirically-driven optimization reduces barrier calls, but is
|
|
||||||
// not guaranteed to be cost-free: wider stage/access masks can increase
|
|
||||||
// cache-visibility or stage-synchronization costs on some vendors, and
|
|
||||||
// the later read waits at the earlier barrier point.
|
|
||||||
let mut barrier_entries: HashMap<AccessRef, BarrierResourceRef> = HashMap::new();
|
|
||||||
|
|
||||||
for (ordered_index, &command_index) in order.iter().enumerate() {
|
|
||||||
let mut barrier_resources = Vec::new();
|
let mut barrier_resources = Vec::new();
|
||||||
let mut emitted_entries = Vec::new();
|
|
||||||
|
|
||||||
for (current_access_index, current) in
|
for current in &self.accesses[command_index.index()] {
|
||||||
self.accesses[command_index.index()].iter().enumerate()
|
// The most recent conflicting access is the complete source scope
|
||||||
{
|
// for this barrier; prior barrier masks are not carried forward.
|
||||||
let current_is_pure_read = access_flag_is_read(current.scope.access)
|
let previous = batches
|
||||||
&& !access_flag_is_write(current.scope.access);
|
|
||||||
|
|
||||||
// Only reads can fold: an overlapping write breaks the read
|
|
||||||
// chain for the affected subresources, while disjoint writes
|
|
||||||
// leave it intact.
|
|
||||||
if current_is_pure_read {
|
|
||||||
let mut folded = false;
|
|
||||||
|
|
||||||
'prior_accesses: for prior_order_index in (0..ordered_index).rev() {
|
|
||||||
let prior_command = order[prior_order_index];
|
|
||||||
for (prior_access_index, prior) in self.accesses[prior_command.index()]
|
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.flatten()
|
||||||
.rev()
|
.take(ordered_index)
|
||||||
.filter(|(_, prior)| prior.resource == current.resource)
|
.filter_map(|&prior_command| {
|
||||||
{
|
self.accesses[prior_command.index()]
|
||||||
if !access_flag_is_write(prior.scope.access) {
|
|
||||||
if access_flag_is_read(prior.scope.access)
|
|
||||||
&& prior.scope.has_same_access_polarity(¤t.scope)
|
|
||||||
&& prior.scope.contains_subresource_range(¤t.scope)
|
|
||||||
{
|
|
||||||
let prior_access = AccessRef {
|
|
||||||
ordered_index: prior_order_index,
|
|
||||||
access_index: prior_access_index,
|
|
||||||
};
|
|
||||||
if let Some(barrier_resource) =
|
|
||||||
barrier_entries.get(&prior_access).copied()
|
|
||||||
{
|
|
||||||
let prior_barrier_resource = barrier_resource.get(&result);
|
|
||||||
// Folding is valid only if the earlier barrier covers
|
|
||||||
// this entire read; widening masks cannot extend its range
|
|
||||||
// or change the layout established at that barrier.
|
|
||||||
if Self::can_fold_read(
|
|
||||||
prior.scope,
|
|
||||||
current.scope,
|
|
||||||
prior_barrier_resource,
|
|
||||||
) {
|
|
||||||
barrier_resource.widen(
|
|
||||||
&mut result,
|
|
||||||
current.scope.stage,
|
|
||||||
current.scope.access,
|
|
||||||
);
|
|
||||||
folded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if folded {
|
|
||||||
break 'prior_accesses;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the nearest containing read. If it
|
|
||||||
// cannot fold, normal hazard/initial-state
|
|
||||||
// handling must decide what barrier is needed.
|
|
||||||
break 'prior_accesses;
|
|
||||||
}
|
|
||||||
} else if prior.scope.overlaps_subresource(¤t.scope) {
|
|
||||||
// Only a write overlapping this read's range breaks
|
|
||||||
// its chain; disjoint writes do not affect containment.
|
|
||||||
break 'prior_accesses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if folded {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut previous = None;
|
|
||||||
let mut src_stage = vk::PipelineStageFlags2::NONE;
|
|
||||||
let mut src_access = vk::AccessFlags2::NONE;
|
|
||||||
|
|
||||||
// Accumulate every outstanding conflicting source, newest first.
|
|
||||||
// A prior barrier is a chain point only for the subresources its
|
|
||||||
// range covers. For partial coverage, conservatively keep scanning
|
|
||||||
// and include every older conflicting access; source masks do not
|
|
||||||
// encode which part of the current range they made available.
|
|
||||||
'prior_barriers: for prior_order_index in (0..ordered_index).rev() {
|
|
||||||
let prior_command = order[prior_order_index];
|
|
||||||
for (prior_access_index, prior) in self.accesses[prior_command.index()]
|
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev()
|
.rev()
|
||||||
.filter(|(_, prior)| {
|
.find(|prior| {
|
||||||
prior.resource == current.resource
|
prior.resource == current.resource
|
||||||
&& prior.scope.conflicts(¤t.scope)
|
&& prior.scope.conflicts(¤t.scope)
|
||||||
})
|
})
|
||||||
{
|
.map(|prior| (prior_command, prior))
|
||||||
if previous.is_none() {
|
})
|
||||||
previous = Some((prior_command, prior));
|
.last();
|
||||||
}
|
|
||||||
|
|
||||||
src_stage |= prior.scope.stage;
|
|
||||||
src_access |= prior.scope.access;
|
|
||||||
|
|
||||||
let prior_access = AccessRef {
|
|
||||||
ordered_index: prior_order_index,
|
|
||||||
access_index: prior_access_index,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(barrier_resource) = barrier_entries.get(&prior_access).copied()
|
|
||||||
{
|
|
||||||
// The prior barrier's source masks carry the older
|
|
||||||
// availability chain; include them in this source scope.
|
|
||||||
let prior_barrier = barrier_resource.get(&result);
|
|
||||||
src_stage |= prior_barrier.src_stage;
|
|
||||||
src_access |= prior_barrier.src_access;
|
|
||||||
|
|
||||||
if prior_barrier.range.contains_scope(¤t.scope) {
|
|
||||||
break 'prior_barriers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((previous_command, previous)) = previous {
|
if let Some((previous_command, previous)) = previous {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
|
|
@ -539,15 +388,13 @@ impl<'a> GraphCompiler<'a> {
|
||||||
|
|
||||||
// Layouts follow the latest conflicting access, which is
|
// Layouts follow the latest conflicting access, which is
|
||||||
// the state the resource actually reached most recently.
|
// the state the resource actually reached most recently.
|
||||||
// TODO: Define a range-aware policy if accumulated sources
|
|
||||||
// with different layouts can overlap only partially.
|
|
||||||
let (old_layout, new_layout) = Self::layouts(previous.scope, current.scope);
|
let (old_layout, new_layout) = Self::layouts(previous.scope, current.scope);
|
||||||
|
|
||||||
barrier_resources.push(BarrierResource {
|
barrier_resources.push(BarrierResource {
|
||||||
id: current.resource,
|
id: current.resource,
|
||||||
range: scope_range(current.scope),
|
range: scope_range(current.scope),
|
||||||
src_stage,
|
src_stage: previous.scope.stage,
|
||||||
src_access,
|
src_access: previous.scope.access,
|
||||||
dst_stage: current.scope.stage,
|
dst_stage: current.scope.stage,
|
||||||
dst_access: current.scope.access,
|
dst_access: current.scope.access,
|
||||||
old_layout,
|
old_layout,
|
||||||
|
|
@ -555,17 +402,6 @@ impl<'a> GraphCompiler<'a> {
|
||||||
queue_transfer: None,
|
queue_transfer: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
emitted_entries.push(EmittedBarrierEntry {
|
|
||||||
access: AccessRef {
|
|
||||||
ordered_index,
|
|
||||||
access_index: current_access_index,
|
|
||||||
},
|
|
||||||
resource: BarrierResourceRef {
|
|
||||||
barrier_index: result.len(),
|
|
||||||
resource_index: barrier_resources.len() - 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -595,24 +431,10 @@ impl<'a> GraphCompiler<'a> {
|
||||||
new_layout,
|
new_layout,
|
||||||
queue_transfer: None,
|
queue_transfer: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
emitted_entries.push(EmittedBarrierEntry {
|
|
||||||
access: AccessRef {
|
|
||||||
ordered_index,
|
|
||||||
access_index: current_access_index,
|
|
||||||
},
|
|
||||||
resource: BarrierResourceRef {
|
|
||||||
barrier_index: result.len(),
|
|
||||||
resource_index: barrier_resources.len() - 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !barrier_resources.is_empty() {
|
if !barrier_resources.is_empty() {
|
||||||
for entry in emitted_entries {
|
|
||||||
barrier_entries.insert(entry.access, entry.resource);
|
|
||||||
}
|
|
||||||
result.push(PipelineBarrier {
|
result.push(PipelineBarrier {
|
||||||
before_command: command_index,
|
before_command: command_index,
|
||||||
resources: barrier_resources,
|
resources: barrier_resources,
|
||||||
|
|
@ -647,18 +469,6 @@ impl<'a> GraphCompiler<'a> {
|
||||||
(initial.layout, current_layout)
|
(initial.layout, current_layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_fold_read(
|
|
||||||
prior: SubresourceScope,
|
|
||||||
current: SubresourceScope,
|
|
||||||
barrier: &BarrierResource,
|
|
||||||
) -> bool {
|
|
||||||
let (_, prior_layout) = Self::layouts(prior, prior);
|
|
||||||
let (_, current_layout) = Self::layouts(prior, current);
|
|
||||||
prior_layout == current_layout
|
|
||||||
&& barrier.new_layout == current_layout
|
|
||||||
&& barrier.range.contains_scope(¤t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bind_lifetimes(&self) -> (HashMap<ResourceId, ResourceLifetime>, ResourceInterference) {
|
fn bind_lifetimes(&self) -> (HashMap<ResourceId, ResourceLifetime>, ResourceInterference) {
|
||||||
let mut lifetimes = HashMap::new();
|
let mut lifetimes = HashMap::new();
|
||||||
for (command, command_accesses) in self.accesses.iter().enumerate() {
|
for (command, command_accesses) in self.accesses.iter().enumerate() {
|
||||||
|
|
@ -732,7 +542,6 @@ pub struct CompiledGraph {
|
||||||
resolved: HashMap<ResourceId, ResolvedResource>,
|
resolved: HashMap<ResourceId, ResolvedResource>,
|
||||||
commands: CommandList,
|
commands: CommandList,
|
||||||
batches: Vec<Vec<CommandIndex>>,
|
batches: Vec<Vec<CommandIndex>>,
|
||||||
flat_order: Vec<CommandIndex>,
|
|
||||||
barriers: Vec<PipelineBarrier>,
|
barriers: Vec<PipelineBarrier>,
|
||||||
lifetimes: HashMap<ResourceId, ResourceLifetime>,
|
lifetimes: HashMap<ResourceId, ResourceLifetime>,
|
||||||
interference: ResourceInterference,
|
interference: ResourceInterference,
|
||||||
|
|
@ -748,10 +557,6 @@ impl CompiledGraph {
|
||||||
&self.batches
|
&self.batches
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn command_order(&self) -> &[CommandIndex] {
|
|
||||||
&self.flat_order
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn barriers(&self) -> &[PipelineBarrier] {
|
pub fn barriers(&self) -> &[PipelineBarrier] {
|
||||||
&self.barriers
|
&self.barriers
|
||||||
}
|
}
|
||||||
|
|
@ -1260,7 +1065,15 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
let compiled = graph.compile().expect("graph compiles");
|
let compiled = graph.compile().expect("graph compiles");
|
||||||
assert_eq!(compiled.command_order(), &[CommandIndex(0)]);
|
assert_eq!(
|
||||||
|
compiled
|
||||||
|
.batches()
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![CommandIndex(0)]
|
||||||
|
);
|
||||||
assert!(compiled.barriers().is_empty());
|
assert!(compiled.barriers().is_empty());
|
||||||
assert_eq!(compiled.resource_lifetimes().len(), 2);
|
assert_eq!(compiled.resource_lifetimes().len(), 2);
|
||||||
assert!(compiled.interference().interferes(src.id(), dst.id()));
|
assert!(compiled.interference().interferes(src.id(), dst.id()));
|
||||||
|
|
@ -1308,8 +1121,13 @@ mod tests {
|
||||||
|
|
||||||
let compiled = graph.compile().expect("graph compiles");
|
let compiled = graph.compile().expect("graph compiles");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compiled.command_order(),
|
compiled
|
||||||
&[CommandIndex(0), CommandIndex(1)]
|
.batches()
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![CommandIndex(0), CommandIndex(1)]
|
||||||
);
|
);
|
||||||
assert_eq!(compiled.barriers().len(), 1);
|
assert_eq!(compiled.barriers().len(), 1);
|
||||||
assert_eq!(compiled.barriers()[0].before_command, CommandIndex(1));
|
assert_eq!(compiled.barriers()[0].before_command, CommandIndex(1));
|
||||||
|
|
@ -1317,7 +1135,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_read_write_barrier_accumulates_outstanding_write_and_read_sources() {
|
fn write_read_write_emits_barriers_from_each_most_recent_conflict() {
|
||||||
let resource = ResourceId(0);
|
let resource = ResourceId(0);
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
||||||
size: 64,
|
size: 64,
|
||||||
|
|
@ -1348,8 +1166,26 @@ mod tests {
|
||||||
];
|
];
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
let batches = compiler.topo_batches().expect("graph is acyclic");
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
let barriers = compiler.build_barriers(&batches);
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
assert_eq!(barriers.len(), 2);
|
||||||
|
assert_eq!(barriers[0].before_command, CommandIndex(1));
|
||||||
|
assert_eq!(
|
||||||
|
barriers[0].resources[0].src_stage,
|
||||||
|
vk::PipelineStageFlags2::COPY
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
barriers[0].resources[0].src_access,
|
||||||
|
vk::AccessFlags2::TRANSFER_WRITE
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
barriers[0].resources[0].dst_stage,
|
||||||
|
vk::PipelineStageFlags2::VERTEX_SHADER
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
barriers[0].resources[0].dst_access,
|
||||||
|
vk::AccessFlags2::SHADER_READ
|
||||||
|
);
|
||||||
|
|
||||||
let second_write_barrier = barriers
|
let second_write_barrier = barriers
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1357,114 +1193,11 @@ mod tests {
|
||||||
.expect("second write has a barrier");
|
.expect("second write has a barrier");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
second_write_barrier.resources[0].src_stage,
|
second_write_barrier.resources[0].src_stage,
|
||||||
vk::PipelineStageFlags2::COPY | vk::PipelineStageFlags2::VERTEX_SHADER
|
vk::PipelineStageFlags2::VERTEX_SHADER
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
second_write_barrier.resources[0].src_access,
|
second_write_barrier.resources[0].src_access,
|
||||||
vk::AccessFlags2::TRANSFER_WRITE | vk::AccessFlags2::SHADER_READ
|
vk::AccessFlags2::SHADER_READ
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn partial_barrier_coverage_keeps_accumulating_older_write_sources() {
|
|
||||||
let resource = ResourceId(0);
|
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
|
||||||
size: 64,
|
|
||||||
..Default::default()
|
|
||||||
})];
|
|
||||||
let accesses = vec![
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::COPY,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
16,
|
|
||||||
vk::PipelineStageFlags2::COMPUTE_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
|
||||||
)],
|
|
||||||
];
|
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
let third_write_barrier = barriers
|
|
||||||
.iter()
|
|
||||||
.find(|barrier| barrier.before_command == CommandIndex(2))
|
|
||||||
.expect("third write has a barrier");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
third_write_barrier.resources[0].src_stage,
|
|
||||||
vk::PipelineStageFlags2::COPY | vk::PipelineStageFlags2::COMPUTE_SHADER
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
third_write_barrier.resources[0].src_access,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE | vk::AccessFlags2::SHADER_WRITE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn disjoint_write_does_not_break_contained_read_barrier_folding() {
|
|
||||||
let resource = ResourceId(0);
|
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
|
||||||
size: 128,
|
|
||||||
..Default::default()
|
|
||||||
})];
|
|
||||||
let accesses = vec![
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
32,
|
|
||||||
vk::PipelineStageFlags2::COPY,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
32,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_READ,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
64,
|
|
||||||
32,
|
|
||||||
vk::PipelineStageFlags2::COMPUTE_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
8,
|
|
||||||
8,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
vk::AccessFlags2::UNIFORM_READ,
|
|
||||||
)],
|
|
||||||
];
|
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
assert_eq!(barriers.len(), 1);
|
|
||||||
assert_eq!(barriers[0].before_command, CommandIndex(1));
|
|
||||||
assert_eq!(
|
|
||||||
barriers[0].resources[0].dst_stage,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
barriers[0].resources[0].dst_access,
|
|
||||||
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::UNIFORM_READ
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1502,9 +1235,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
let batches = compiler.topo_batches().expect("graph is acyclic");
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
let barriers = compiler.build_barriers_with_initial_states(&batches, &initial_states);
|
||||||
let barriers =
|
|
||||||
compiler.build_barriers_with_initial_states(&order, &batches, &initial_states);
|
|
||||||
|
|
||||||
assert_eq!(barriers.len(), 1);
|
assert_eq!(barriers.len(), 1);
|
||||||
assert_eq!(barriers[0].before_command, CommandIndex(1));
|
assert_eq!(barriers[0].before_command, CommandIndex(1));
|
||||||
|
|
@ -1523,255 +1254,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn contained_read_folds_into_prior_read_barrier_destination_masks() {
|
fn overlapping_texture_reads_with_different_layouts_get_layout_transition() {
|
||||||
let resource = ResourceId(0);
|
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
|
||||||
size: 64,
|
|
||||||
..Default::default()
|
|
||||||
})];
|
|
||||||
let accesses = vec![
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::COPY,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_READ,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
16,
|
|
||||||
16,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
vk::AccessFlags2::UNIFORM_READ,
|
|
||||||
)],
|
|
||||||
];
|
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
assert_eq!(barriers.len(), 1);
|
|
||||||
assert_eq!(barriers[0].before_command, CommandIndex(1));
|
|
||||||
assert_eq!(barriers[0].resources.len(), 1);
|
|
||||||
assert_eq!(
|
|
||||||
barriers[0].resources[0].dst_stage,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
barriers[0].resources[0].dst_access,
|
|
||||||
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::UNIFORM_READ
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn narrow_prior_barrier_does_not_cover_a_later_larger_read() {
|
|
||||||
let resource = ResourceId(0);
|
|
||||||
let prior = SubresourceScope {
|
|
||||||
access: vk::AccessFlags2::SHADER_READ,
|
|
||||||
stage: vk::PipelineStageFlags2::VERTEX_SHADER,
|
|
||||||
subresource: SubresourceScopeInner::Buffer(BufferRange {
|
|
||||||
offset: 0,
|
|
||||||
size: 64,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
let current = SubresourceScope {
|
|
||||||
access: vk::AccessFlags2::UNIFORM_READ,
|
|
||||||
stage: vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
subresource: SubresourceScopeInner::Buffer(BufferRange {
|
|
||||||
offset: 0,
|
|
||||||
size: 32,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
let narrow_barrier = BarrierResource {
|
|
||||||
id: resource,
|
|
||||||
range: Subresource::Buffer(BufferRange {
|
|
||||||
offset: 0,
|
|
||||||
size: 16,
|
|
||||||
}),
|
|
||||||
src_stage: vk::PipelineStageFlags2::COPY,
|
|
||||||
src_access: vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
dst_stage: prior.stage,
|
|
||||||
dst_access: prior.access,
|
|
||||||
old_layout: None,
|
|
||||||
new_layout: None,
|
|
||||||
queue_transfer: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(prior.contains_subresource_range(¤t));
|
|
||||||
assert!(!GraphCompiler::can_fold_read(
|
|
||||||
prior,
|
|
||||||
current,
|
|
||||||
&narrow_barrier
|
|
||||||
));
|
|
||||||
|
|
||||||
// The prior scope contains the later read and the layouts match; only
|
|
||||||
// the synthetic barrier's narrower range invalidates folding.
|
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
|
||||||
size: 64,
|
|
||||||
..Default::default()
|
|
||||||
})];
|
|
||||||
let accesses = vec![
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::COPY,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
16,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_READ,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
vk::AccessFlags2::UNIFORM_READ,
|
|
||||||
)],
|
|
||||||
];
|
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
let later_read_barrier = barriers
|
|
||||||
.iter()
|
|
||||||
.find(|barrier| barrier.before_command == CommandIndex(2))
|
|
||||||
.expect("larger later read gets its own barrier");
|
|
||||||
assert_eq!(later_read_barrier.resources[0].dst_stage, current.stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn intervening_write_prevents_contained_read_barrier_folding() {
|
|
||||||
let resource = ResourceId(0);
|
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
|
||||||
size: 64,
|
|
||||||
..Default::default()
|
|
||||||
})];
|
|
||||||
let accesses = vec![
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::COPY,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_READ,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
vk::PipelineStageFlags2::COMPUTE_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![scoped_access(
|
|
||||||
resource,
|
|
||||||
16,
|
|
||||||
16,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_READ,
|
|
||||||
)],
|
|
||||||
];
|
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
assert_eq!(barriers.len(), 3);
|
|
||||||
assert_eq!(barriers[0].before_command, CommandIndex(1));
|
|
||||||
assert_eq!(barriers[1].before_command, CommandIndex(2));
|
|
||||||
assert_eq!(barriers[2].before_command, CommandIndex(3));
|
|
||||||
assert_eq!(
|
|
||||||
barriers[2].resources[0].dst_stage,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn contained_texture_read_folds_aspect_mip_and_layer_ranges() {
|
|
||||||
let resource = ResourceId(0);
|
|
||||||
let resources = [ResourceDesc::CreatedTexture(GraphTextureDesc {
|
|
||||||
format: vk::Format::R8G8B8A8_UNORM,
|
|
||||||
mip_levels: 4,
|
|
||||||
array_layers: 8,
|
|
||||||
..Default::default()
|
|
||||||
})];
|
|
||||||
let accesses = vec![
|
|
||||||
vec![texture_scoped_access(
|
|
||||||
resource,
|
|
||||||
texture_range(
|
|
||||||
vk::ImageAspectFlags::COLOR | vk::ImageAspectFlags::DEPTH,
|
|
||||||
0,
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
vk::ImageLayout::GENERAL,
|
|
||||||
vk::PipelineStageFlags2::COPY,
|
|
||||||
vk::AccessFlags2::TRANSFER_WRITE,
|
|
||||||
)],
|
|
||||||
vec![texture_scoped_access(
|
|
||||||
resource,
|
|
||||||
texture_range(
|
|
||||||
vk::ImageAspectFlags::COLOR | vk::ImageAspectFlags::DEPTH,
|
|
||||||
0,
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_READ,
|
|
||||||
)],
|
|
||||||
vec![texture_scoped_access(
|
|
||||||
resource,
|
|
||||||
texture_range(vk::ImageAspectFlags::COLOR, 1, 2, 2, 3),
|
|
||||||
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
|
||||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
|
||||||
vk::AccessFlags2::SHADER_SAMPLED_READ,
|
|
||||||
)],
|
|
||||||
];
|
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
assert_eq!(barriers.len(), 2);
|
|
||||||
assert_eq!(barriers[0].before_command, CommandIndex(0));
|
|
||||||
assert_eq!(barriers[1].before_command, CommandIndex(1));
|
|
||||||
let barrier = &barriers[1].resources[0];
|
|
||||||
assert_eq!(
|
|
||||||
barrier.dst_stage,
|
|
||||||
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
barrier.dst_access,
|
|
||||||
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::SHADER_SAMPLED_READ
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(barrier.range, Subresource::Texture(range) if range.aspect_mask == vk::ImageAspectFlags::COLOR | vk::ImageAspectFlags::DEPTH && range.base_mip_level == 0 && range.level_count == 4 && range.base_array_layer == 0 && range.layer_count == 8)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn overlapping_texture_reads_with_different_layouts_are_not_folded() {
|
|
||||||
let resource = ResourceId(0);
|
let resource = ResourceId(0);
|
||||||
let resources = [ResourceDesc::CreatedTexture(GraphTextureDesc {
|
let resources = [ResourceDesc::CreatedTexture(GraphTextureDesc {
|
||||||
format: vk::Format::R8G8B8A8_UNORM,
|
format: vk::Format::R8G8B8A8_UNORM,
|
||||||
|
|
@ -1803,8 +1286,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
let batches = compiler.topo_batches().expect("graph is acyclic");
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
let barriers = compiler.build_barriers(&batches);
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
assert_eq!(barriers.len(), 3);
|
assert_eq!(barriers.len(), 3);
|
||||||
let current_read_barrier = &barriers[2];
|
let current_read_barrier = &barriers[2];
|
||||||
|
|
@ -1820,7 +1302,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pure_read_without_prior_barrier_entry_needs_no_barrier() {
|
fn overlapping_pure_reads_without_layout_change_need_no_barrier() {
|
||||||
let resource = ResourceId(0);
|
let resource = ResourceId(0);
|
||||||
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
let resources = [ResourceDesc::CreatedBuffer(GraphBufferDesc {
|
||||||
size: 64,
|
size: 64,
|
||||||
|
|
@ -1844,9 +1326,8 @@ mod tests {
|
||||||
];
|
];
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
let batches = compiler.topo_batches().expect("graph is acyclic");
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert!(compiler.build_barriers(&order, &batches).is_empty());
|
assert!(compiler.build_barriers(&batches).is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1874,8 +1355,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
let compiler = GraphCompiler::new(&resources, &accesses, accesses.len());
|
||||||
let batches = compiler.topo_batches().expect("graph is acyclic");
|
let batches = compiler.topo_batches().expect("graph is acyclic");
|
||||||
let order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
let barriers = compiler.build_barriers(&batches);
|
||||||
let barriers = compiler.build_barriers(&order, &batches);
|
|
||||||
|
|
||||||
assert_eq!(batches, [vec![CommandIndex(0)], vec![CommandIndex(1)]]);
|
assert_eq!(batches, [vec![CommandIndex(0)], vec![CommandIndex(1)]]);
|
||||||
assert_eq!(barriers.len(), 1);
|
assert_eq!(barriers.len(), 1);
|
||||||
|
|
@ -1946,8 +1426,13 @@ mod tests {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compiled.command_order(),
|
compiled
|
||||||
[CommandIndex(0), CommandIndex(2), CommandIndex(1)]
|
.batches()
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![CommandIndex(0), CommandIndex(2), CommandIndex(1)]
|
||||||
);
|
);
|
||||||
assert_eq!(compiled.barriers().len(), 1);
|
assert_eq!(compiled.barriers().len(), 1);
|
||||||
assert_eq!(compiled.barriers()[0].before_command, CommandIndex(1));
|
assert_eq!(compiled.barriers()[0].before_command, CommandIndex(1));
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ impl SideEffectMap {
|
||||||
/// Record an access scope for a command and resource.
|
/// Record an access scope for a command and resource.
|
||||||
///
|
///
|
||||||
/// Each scope must describe the command's complete use for that access,
|
/// Each scope must describe the command's complete use for that access,
|
||||||
/// not merely an enclosing bound. In particular, the reverse containment
|
/// not merely an enclosing bound, because `SideEffectMap::insert` may merge
|
||||||
/// fold is sound only when the larger scope represents the complete use.
|
/// contained scopes for the same command and resource.
|
||||||
pub fn insert(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
command_index: CommandIndex,
|
command_index: CommandIndex,
|
||||||
|
|
|
||||||
|
|
@ -136,50 +136,6 @@ pub enum Subresource {
|
||||||
Texture(vk::ImageSubresourceRange),
|
Texture(vk::ImageSubresourceRange),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subresource {
|
|
||||||
/// Whether this barrier range fully covers the scope's subresource range.
|
|
||||||
pub fn contains_scope(self, scope: &SubresourceScope) -> bool {
|
|
||||||
match (self, scope.subresource) {
|
|
||||||
(Self::Buffer(range), SubresourceScopeInner::Buffer(other)) => range.contains(&other),
|
|
||||||
(Self::Texture(range), SubresourceScopeInner::Texture { range: other, .. }) => {
|
|
||||||
range.aspect_mask.contains(other.aspect_mask)
|
|
||||||
&& range_contains(
|
|
||||||
range.base_mip_level,
|
|
||||||
range.level_count,
|
|
||||||
other.base_mip_level,
|
|
||||||
other.level_count,
|
|
||||||
vk::REMAINING_MIP_LEVELS,
|
|
||||||
)
|
|
||||||
&& range_contains(
|
|
||||||
range.base_array_layer,
|
|
||||||
range.layer_count,
|
|
||||||
other.base_array_layer,
|
|
||||||
other.layer_count,
|
|
||||||
vk::REMAINING_ARRAY_LAYERS,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn range_contains(
|
|
||||||
base: u32,
|
|
||||||
count: u32,
|
|
||||||
other_base: u32,
|
|
||||||
other_count: u32,
|
|
||||||
remaining: u32,
|
|
||||||
) -> bool {
|
|
||||||
let end = |base: u32, count: u32| {
|
|
||||||
if count == remaining {
|
|
||||||
u64::MAX
|
|
||||||
} else {
|
|
||||||
u64::from(base) + u64::from(count)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
base <= other_base && end(base, count) >= end(other_base, other_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum SubresourceScopeInner {
|
pub enum SubresourceScopeInner {
|
||||||
Buffer(BufferRange),
|
Buffer(BufferRange),
|
||||||
|
|
@ -197,43 +153,21 @@ pub struct SubresourceScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubresourceScope {
|
impl SubresourceScope {
|
||||||
/// Whether the scopes refer to any common buffer bytes or texture
|
/// Whether this scope contains the other's subresource range.
|
||||||
/// subresources, regardless of access mode or image layout.
|
|
||||||
pub fn overlaps_subresource(&self, other: &Self) -> bool {
|
|
||||||
match (self.subresource, other.subresource) {
|
|
||||||
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.overlaps(b),
|
|
||||||
(
|
|
||||||
SubresourceScopeInner::Texture { range: a, .. },
|
|
||||||
SubresourceScopeInner::Texture { range: b, .. },
|
|
||||||
) => a.intersects(&b),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this scope's subresource contains the other's subresource.
|
|
||||||
/// Texture ranges are compatible only when their layouts match.
|
/// Texture ranges are compatible only when their layouts match.
|
||||||
pub fn contains_subresource(&self, other: &Self) -> bool {
|
pub fn contains_subresource(&self, other: &Self) -> bool {
|
||||||
self.contains_subresource_range(other)
|
|
||||||
&& match (self.subresource, other.subresource) {
|
|
||||||
(
|
|
||||||
SubresourceScopeInner::Texture { layout: a, .. },
|
|
||||||
SubresourceScopeInner::Texture { layout: b, .. },
|
|
||||||
) => a == b,
|
|
||||||
(SubresourceScopeInner::Buffer(_), SubresourceScopeInner::Buffer(_)) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this scope's range contains the other's, without considering
|
|
||||||
/// texture layout. Use this to find candidates whose layout must be checked
|
|
||||||
/// separately (for example, when deciding whether a read can fold).
|
|
||||||
pub fn contains_subresource_range(&self, other: &Self) -> bool {
|
|
||||||
match (self.subresource, other.subresource) {
|
match (self.subresource, other.subresource) {
|
||||||
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.contains(&b),
|
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.contains(&b),
|
||||||
(
|
(
|
||||||
SubresourceScopeInner::Texture { range: a, .. },
|
SubresourceScopeInner::Texture {
|
||||||
SubresourceScopeInner::Texture { range: b, .. },
|
range: a,
|
||||||
) => a.contains(&b),
|
layout: layout_a,
|
||||||
|
},
|
||||||
|
SubresourceScopeInner::Texture {
|
||||||
|
range: b,
|
||||||
|
layout: layout_b,
|
||||||
|
},
|
||||||
|
) => layout_a == layout_b && a.contains(&b),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +178,7 @@ impl SubresourceScope {
|
||||||
&& access_flag_is_write(self.access) == access_flag_is_write(other.access)
|
&& access_flag_is_write(self.access) == access_flag_is_write(other.access)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fold sub-ranges with compatible access polarity into one scope.
|
/// Merge sub-ranges with compatible access polarity into one scope.
|
||||||
pub fn try_merge(&mut self, other: &Self) -> bool {
|
pub fn try_merge(&mut self, other: &Self) -> bool {
|
||||||
if access_flag_is_read(self.access) != access_flag_is_read(other.access)
|
if access_flag_is_read(self.access) != access_flag_is_read(other.access)
|
||||||
|| access_flag_is_write(self.access) != access_flag_is_write(other.access)
|
|| access_flag_is_write(self.access) != access_flag_is_write(other.access)
|
||||||
|
|
|
||||||
|
|
@ -1127,10 +1127,10 @@ impl ImageSubresourceExt for vk::ImageSubresource {
|
||||||
self.aspect_mask
|
self.aspect_mask
|
||||||
}
|
}
|
||||||
fn layers(&self) -> MipRange {
|
fn layers(&self) -> MipRange {
|
||||||
MipRange::single(self.mip_level)
|
MipRange::single(self.array_layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mip_levels(&self) -> MipRange {
|
fn mip_levels(&self) -> MipRange {
|
||||||
MipRange::single(self.array_layer)
|
MipRange::single(self.mip_level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue