response to llm review
This commit is contained in:
parent
c0f60b890c
commit
f84b2a1ea4
|
|
@ -156,14 +156,15 @@ impl Kahn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_ready(&mut self) -> Option<usize> {
|
fn pop_batch(&mut self) -> Vec<usize> {
|
||||||
|
let mut batch = Vec::new();
|
||||||
while let Some(Reverse(node)) = self.ready.pop() {
|
while let Some(Reverse(node)) = self.ready.pop() {
|
||||||
if self.is_ready[node] {
|
if self.is_ready[node] {
|
||||||
self.is_ready[node] = false;
|
self.is_ready[node] = false;
|
||||||
return Some(node);
|
batch.push(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
batch
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(&mut self, node: usize) {
|
fn complete(&mut self, node: usize) {
|
||||||
|
|
@ -242,16 +243,18 @@ impl RenderGraph {
|
||||||
let command_count = self.commands.len();
|
let command_count = self.commands.len();
|
||||||
let accesses = self.commands.side_effects.command_accesses();
|
let accesses = self.commands.side_effects.command_accesses();
|
||||||
let compiler = GraphCompiler::new(&self.resources, accesses, command_count);
|
let compiler = GraphCompiler::new(&self.resources, accesses, command_count);
|
||||||
let order = compiler.topo_sort()?;
|
let batches = compiler.topo_batches()?;
|
||||||
compiler.validate_resources()?;
|
compiler.validate_resources()?;
|
||||||
let barriers = compiler.build_barriers(&order);
|
let flat_order = batches.iter().flatten().copied().collect::<Vec<_>>();
|
||||||
|
let barriers = compiler.build_barriers(&flat_order, &batches);
|
||||||
let (lifetimes, interference) = compiler.bind_lifetimes();
|
let (lifetimes, interference) = compiler.bind_lifetimes();
|
||||||
|
|
||||||
Ok(CompiledGraph {
|
Ok(CompiledGraph {
|
||||||
resources: self.resources,
|
resources: self.resources,
|
||||||
resolved: HashMap::new(),
|
resolved: HashMap::new(),
|
||||||
commands: self.commands,
|
commands: self.commands,
|
||||||
order,
|
batches,
|
||||||
|
flat_order,
|
||||||
barriers,
|
barriers,
|
||||||
lifetimes,
|
lifetimes,
|
||||||
interference,
|
interference,
|
||||||
|
|
@ -296,17 +299,22 @@ impl<'a> GraphCompiler<'a> {
|
||||||
kahn
|
kahn
|
||||||
}
|
}
|
||||||
|
|
||||||
fn topo_sort(&self) -> Result<Vec<CommandIndex>, GraphError> {
|
fn topo_batches(&self) -> Result<Vec<Vec<CommandIndex>>, GraphError> {
|
||||||
let mut kahn = self.dependencies();
|
let mut kahn = self.dependencies();
|
||||||
let mut order = Vec::with_capacity(self.command_count);
|
let mut batches = Vec::new();
|
||||||
while let Some(index) = kahn.pop_ready() {
|
while !kahn.is_complete() {
|
||||||
order.push(index.into());
|
let batch = kahn.pop_batch();
|
||||||
kahn.complete(index);
|
if batch.is_empty() {
|
||||||
}
|
|
||||||
if !kahn.is_complete() {
|
|
||||||
return Err(GraphError::DependencyCycle);
|
return Err(GraphError::DependencyCycle);
|
||||||
}
|
}
|
||||||
Ok(order)
|
let batch = batch
|
||||||
|
.into_iter()
|
||||||
|
.inspect(|&index| kahn.complete(index))
|
||||||
|
.map(CommandIndex::from)
|
||||||
|
.collect();
|
||||||
|
batches.push(batch);
|
||||||
|
}
|
||||||
|
Ok(batches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_resources(&self) -> Result<(), GraphError> {
|
fn validate_resources(&self) -> Result<(), GraphError> {
|
||||||
|
|
@ -320,7 +328,17 @@ impl<'a> GraphCompiler<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_barriers(&self, order: &[CommandIndex]) -> Vec<PipelineBarrier> {
|
fn build_barriers(
|
||||||
|
&self,
|
||||||
|
order: &[CommandIndex],
|
||||||
|
batches: &[Vec<CommandIndex>],
|
||||||
|
) -> Vec<PipelineBarrier> {
|
||||||
|
let mut batch_indices = vec![usize::MAX; self.command_count];
|
||||||
|
for (batch_index, batch) in batches.iter().enumerate() {
|
||||||
|
for command in batch {
|
||||||
|
batch_indices[command.index()] = batch_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for (ordered_index, &command_index) in order.iter().enumerate() {
|
for (ordered_index, &command_index) in order.iter().enumerate() {
|
||||||
let mut barrier_resources = Vec::new();
|
let mut barrier_resources = Vec::new();
|
||||||
|
|
@ -328,13 +346,25 @@ impl<'a> GraphCompiler<'a> {
|
||||||
let previous = order[..ordered_index]
|
let previous = order[..ordered_index]
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.flat_map(|&prior_index| self.accesses[prior_index.index()].iter().rev())
|
.find_map(|&prior_index| {
|
||||||
|
self.accesses[prior_index.index()]
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
.find(|previous| {
|
.find(|previous| {
|
||||||
previous.resource == current.resource
|
previous.resource == current.resource
|
||||||
&& previous.scope.conflicts(¤t.scope)
|
&& previous.scope.conflicts(¤t.scope)
|
||||||
|
})
|
||||||
|
.map(|access| (prior_index, access))
|
||||||
});
|
});
|
||||||
if let Some(previous) = previous {
|
|
||||||
|
if let Some((previous_command, previous)) = previous {
|
||||||
|
debug_assert!(
|
||||||
|
batch_indices[previous_command.index()]
|
||||||
|
< batch_indices[command_index.index()]
|
||||||
|
);
|
||||||
|
|
||||||
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),
|
||||||
|
|
@ -345,19 +375,24 @@ impl<'a> GraphCompiler<'a> {
|
||||||
old_layout,
|
old_layout,
|
||||||
new_layout,
|
new_layout,
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let desc = &self.resources[current.resource.0 as usize];
|
let desc = &self.resources[current.resource.0 as usize];
|
||||||
let initial = desc.initial_state();
|
let initial = desc.initial_state();
|
||||||
|
|
||||||
let (old_layout, new_layout) = Self::layouts_from_state(initial, current.scope);
|
let (old_layout, new_layout) = Self::layouts_from_state(initial, current.scope);
|
||||||
|
|
||||||
let layout_transition = old_layout != new_layout && new_layout.is_some();
|
let layout_transition = old_layout != new_layout && new_layout.is_some();
|
||||||
|
|
||||||
let initial_hazard = matches!(
|
let initial_hazard = matches!(
|
||||||
desc,
|
desc,
|
||||||
ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. }
|
ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. }
|
||||||
) && !initial.access.is_empty()
|
) && !initial.access.is_empty()
|
||||||
&& (access_flag_is_write(initial.access)
|
&& (access_flag_is_write(initial.access)
|
||||||
|| access_flag_is_write(current.scope.access));
|
|| access_flag_is_write(current.scope.access));
|
||||||
|
|
||||||
if layout_transition || initial_hazard {
|
if layout_transition || initial_hazard {
|
||||||
barrier_resources.push(BarrierResource {
|
barrier_resources.push(BarrierResource {
|
||||||
id: current.resource,
|
id: current.resource,
|
||||||
|
|
@ -371,13 +406,15 @@ impl<'a> GraphCompiler<'a> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !barrier_resources.is_empty() {
|
if !barrier_resources.is_empty() {
|
||||||
result.push(PipelineBarrier {
|
result.push(PipelineBarrier {
|
||||||
before_command: ordered_index.into(),
|
before_command: command_index,
|
||||||
resources: barrier_resources,
|
resources: barrier_resources,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,6 +426,7 @@ impl<'a> GraphCompiler<'a> {
|
||||||
SubresourceScopeInner::Texture { layout, .. } => Some(layout),
|
SubresourceScopeInner::Texture { layout, .. } => Some(layout),
|
||||||
SubresourceScopeInner::Buffer(_) => None,
|
SubresourceScopeInner::Buffer(_) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
(layout(previous), layout(current))
|
(layout(previous), layout(current))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -400,6 +438,7 @@ impl<'a> GraphCompiler<'a> {
|
||||||
SubresourceScopeInner::Texture { layout, .. } => Some(layout),
|
SubresourceScopeInner::Texture { layout, .. } => Some(layout),
|
||||||
SubresourceScopeInner::Buffer(_) => None,
|
SubresourceScopeInner::Buffer(_) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
(initial.layout, current_layout)
|
(initial.layout, current_layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -475,7 +514,8 @@ pub struct CompiledGraph {
|
||||||
resources: Vec<ResourceDesc>,
|
resources: Vec<ResourceDesc>,
|
||||||
resolved: HashMap<ResourceId, ResolvedResource>,
|
resolved: HashMap<ResourceId, ResolvedResource>,
|
||||||
commands: CommandList,
|
commands: CommandList,
|
||||||
order: 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,
|
||||||
|
|
@ -487,8 +527,12 @@ enum ResolvedResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompiledGraph {
|
impl CompiledGraph {
|
||||||
|
pub fn batches(&self) -> &[Vec<CommandIndex>] {
|
||||||
|
&self.batches
|
||||||
|
}
|
||||||
|
|
||||||
pub fn command_order(&self) -> &[CommandIndex] {
|
pub fn command_order(&self) -> &[CommandIndex] {
|
||||||
&self.order
|
&self.flat_order
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn barriers(&self) -> &[PipelineBarrier] {
|
pub fn barriers(&self) -> &[PipelineBarrier] {
|
||||||
|
|
@ -600,20 +644,39 @@ impl CompiledGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut barriers = self.barriers.iter().peekable();
|
// TODO: `batches` are currently recorded sequentially into one command buffer;
|
||||||
for (ordered_index, &command_index) in self.order.iter().enumerate() {
|
// record per-batch command buffers and submit sequentially/in parallel, using split barriers/events or timeline semaphores for overlap.
|
||||||
while barriers
|
for step in recording_steps(&self.batches, &self.barriers) {
|
||||||
.peek()
|
match step {
|
||||||
.is_some_and(|barrier| barrier.before_command == ordered_index.into())
|
// TODO: Merge `PipelineBarrier.resources` across commands or a batch to reduce vkCmdPipelineBarrier2 calls.
|
||||||
{
|
RecordingStep::Barrier(barrier) => recorder.pipeline_barrier(barrier),
|
||||||
recorder.pipeline_barrier(barriers.next().expect("peeked barrier"));
|
RecordingStep::Command(command_index) => {
|
||||||
}
|
|
||||||
self.commands.commands[command_index.index()].apply(recorder)?;
|
self.commands.commands[command_index.index()].apply(recorder)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RecordingStep<'a> {
|
||||||
|
Barrier(&'a PipelineBarrier),
|
||||||
|
Command(CommandIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recording_steps<'a>(
|
||||||
|
batches: &'a [Vec<CommandIndex>],
|
||||||
|
barriers: &'a [PipelineBarrier],
|
||||||
|
) -> impl Iterator<Item = RecordingStep<'a>> + 'a {
|
||||||
|
batches.iter().flatten().flat_map(move |&command_index| {
|
||||||
|
barriers
|
||||||
|
.iter()
|
||||||
|
.filter(move |barrier| barrier.before_command == command_index)
|
||||||
|
.map(RecordingStep::Barrier)
|
||||||
|
.chain(std::iter::once(RecordingStep::Command(command_index)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The skeleton switches to a dedicated allocation at 64 MiB. This is a
|
/// The skeleton switches to a dedicated allocation at 64 MiB. This is a
|
||||||
/// placeholder heuristic until resource lifetime/aliasing allocation is real.
|
/// placeholder heuristic until resource lifetime/aliasing allocation is real.
|
||||||
const DEDICATED_ALLOCATION_THRESHOLD: u64 = 64 * 1024 * 1024;
|
const DEDICATED_ALLOCATION_THRESHOLD: u64 = 64 * 1024 * 1024;
|
||||||
|
|
@ -850,13 +913,17 @@ mod tests {
|
||||||
kahn.add_edge(1, 2);
|
kahn.add_edge(1, 2);
|
||||||
kahn.add_edge(2, 3);
|
kahn.add_edge(2, 3);
|
||||||
|
|
||||||
let mut order = Vec::new();
|
let mut batches = Vec::new();
|
||||||
while let Some(node) = kahn.pop_ready() {
|
while !kahn.is_complete() {
|
||||||
order.push(node);
|
let batch = kahn.pop_batch();
|
||||||
|
assert!(!batch.is_empty());
|
||||||
|
for &node in &batch {
|
||||||
kahn.complete(node);
|
kahn.complete(node);
|
||||||
}
|
}
|
||||||
|
batches.push(batch);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(order, [0, 1, 2, 3]);
|
assert_eq!(batches, [vec![0, 1], vec![2], vec![3]]);
|
||||||
assert!(kahn.is_complete());
|
assert!(kahn.is_complete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -867,7 +934,7 @@ mod tests {
|
||||||
kahn.add_edge(1, 2);
|
kahn.add_edge(1, 2);
|
||||||
kahn.add_edge(2, 1);
|
kahn.add_edge(2, 1);
|
||||||
|
|
||||||
while let Some(node) = kahn.pop_ready() {
|
for node in kahn.pop_batch() {
|
||||||
kahn.complete(node);
|
kahn.complete(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1032,6 +1099,227 @@ mod tests {
|
||||||
assert_eq!(compiled.barriers()[0].resources[0].id, b.id());
|
assert_eq!(compiled.barriers()[0].resources[0].id, b.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn independent_commands_share_a_batch_and_dependencies_advance_to_later_batches() {
|
||||||
|
let mut graph = RenderGraph::new();
|
||||||
|
let a = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let shared = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let b = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let independent_src = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let independent_dst = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.push(CopyBuffer {
|
||||||
|
src: a,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst: shared,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
});
|
||||||
|
graph.push(CopyBuffer {
|
||||||
|
src: shared,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst: b,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
});
|
||||||
|
graph.push(CopyBuffer {
|
||||||
|
src: independent_src,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst: independent_dst,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
});
|
||||||
|
|
||||||
|
let compiled = graph.compile().expect("graph compiles");
|
||||||
|
assert_eq!(
|
||||||
|
compiled.batches(),
|
||||||
|
&[
|
||||||
|
vec![CommandIndex(0), CommandIndex(2)],
|
||||||
|
vec![CommandIndex(1)]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
compiled.command_order(),
|
||||||
|
[CommandIndex(0), CommandIndex(2), CommandIndex(1)]
|
||||||
|
);
|
||||||
|
assert_eq!(compiled.barriers().len(), 1);
|
||||||
|
assert_eq!(compiled.barriers()[0].before_command, CommandIndex(1));
|
||||||
|
|
||||||
|
let batch_for = |command| {
|
||||||
|
compiled
|
||||||
|
.batches()
|
||||||
|
.iter()
|
||||||
|
.position(|batch| batch.contains(&command))
|
||||||
|
.expect("command appears in a batch")
|
||||||
|
};
|
||||||
|
for barrier in compiled.barriers() {
|
||||||
|
assert!(
|
||||||
|
batch_for(CommandIndex(0)) < batch_for(barrier.before_command),
|
||||||
|
"barrier target follows its conflicting source in another batch"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recording_steps_place_barriers_immediately_before_their_target_command() {
|
||||||
|
let first_barrier = PipelineBarrier {
|
||||||
|
before_command: CommandIndex(1),
|
||||||
|
resources: Vec::new(),
|
||||||
|
};
|
||||||
|
let second_barrier = PipelineBarrier {
|
||||||
|
before_command: CommandIndex(1),
|
||||||
|
resources: Vec::new(),
|
||||||
|
};
|
||||||
|
let barriers = [first_barrier, second_barrier];
|
||||||
|
let batches = [vec![CommandIndex(0), CommandIndex(1)]];
|
||||||
|
|
||||||
|
let steps = recording_steps(&batches, &barriers).collect::<Vec<_>>();
|
||||||
|
assert!(matches!(steps[0], RecordingStep::Command(CommandIndex(0))));
|
||||||
|
assert!(
|
||||||
|
matches!(steps[1], RecordingStep::Barrier(barrier) if std::ptr::eq(barrier, &barriers[0]))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matches!(steps[2], RecordingStep::Barrier(barrier) if std::ptr::eq(barrier, &barriers[1]))
|
||||||
|
);
|
||||||
|
assert!(matches!(steps[3], RecordingStep::Command(CommandIndex(1))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn barrier_for_later_command_follows_independent_command_in_same_batch() {
|
||||||
|
let mut graph = RenderGraph::new();
|
||||||
|
let first_src = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let first_shared = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let first_dst = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let second_src = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let second_shared = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let second_dst = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let copy = |src, dst| CopyBuffer {
|
||||||
|
src,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
};
|
||||||
|
graph.push(copy(first_src, first_shared));
|
||||||
|
graph.push(copy(second_src, second_shared));
|
||||||
|
graph.push(copy(second_shared, second_dst));
|
||||||
|
graph.push(copy(first_shared, first_dst));
|
||||||
|
|
||||||
|
let compiled = graph.compile().expect("graph compiles");
|
||||||
|
assert_eq!(
|
||||||
|
compiled.batches(),
|
||||||
|
&[
|
||||||
|
vec![CommandIndex(0), CommandIndex(1)],
|
||||||
|
vec![CommandIndex(2), CommandIndex(3)]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let steps = recording_steps(compiled.batches(), compiled.barriers()).collect::<Vec<_>>();
|
||||||
|
let command_two_position = steps
|
||||||
|
.iter()
|
||||||
|
.position(|step| matches!(step, RecordingStep::Command(CommandIndex(2))))
|
||||||
|
.expect("independent command is recorded");
|
||||||
|
let barrier_three_position = steps
|
||||||
|
.iter()
|
||||||
|
.position(|step| {
|
||||||
|
matches!(step, RecordingStep::Barrier(barrier) if barrier.before_command == CommandIndex(3))
|
||||||
|
})
|
||||||
|
.expect("barrier for command three is recorded");
|
||||||
|
assert!(command_two_position < barrier_three_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dependency_chain_occupies_three_batches() {
|
||||||
|
let mut graph = RenderGraph::new();
|
||||||
|
let a = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let b = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let c = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let d = graph.create_buffer(GraphBufferDesc {
|
||||||
|
size: 8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
graph.push(CopyBuffer {
|
||||||
|
src: a,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst: b,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
});
|
||||||
|
graph.push(CopyBuffer {
|
||||||
|
src: b,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst: c,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
});
|
||||||
|
graph.push(CopyBuffer {
|
||||||
|
src: c,
|
||||||
|
src_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
dst: d,
|
||||||
|
dst_range: BufferRange { offset: 0, size: 8 },
|
||||||
|
});
|
||||||
|
|
||||||
|
let compiled = graph.compile().expect("graph compiles");
|
||||||
|
assert_eq!(
|
||||||
|
compiled.batches(),
|
||||||
|
&[
|
||||||
|
vec![CommandIndex(0)],
|
||||||
|
vec![CommandIndex(1)],
|
||||||
|
vec![CommandIndex(2)]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
for barrier in compiled.barriers() {
|
||||||
|
let source_command = CommandIndex(barrier.before_command.index() - 1);
|
||||||
|
let source_batch = compiled
|
||||||
|
.batches()
|
||||||
|
.iter()
|
||||||
|
.position(|batch| batch.contains(&source_command))
|
||||||
|
.expect("conflicting source appears in a batch");
|
||||||
|
let target_batch = compiled
|
||||||
|
.batches()
|
||||||
|
.iter()
|
||||||
|
.position(|batch| batch.contains(&barrier.before_command))
|
||||||
|
.expect("barrier target appears in a batch");
|
||||||
|
assert!(source_batch < target_batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn texture_aliasing_size_uses_format_block_size() {
|
fn texture_aliasing_size_uses_format_block_size() {
|
||||||
let mut graph = RenderGraph::new();
|
let mut graph = RenderGraph::new();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue