use std::sync::Arc;
use std::time::Duration;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage};
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
use vulkano::device::{Device, Queue};
use vulkano::memory::allocator::{
AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator,
};
use vulkano::pipeline::compute::ComputePipelineCreateInfo;
use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo;
use vulkano::pipeline::{
ComputePipeline, Pipeline, PipelineBindPoint, PipelineLayout,
PipelineShaderStageCreateInfo,
};
use vulkano::sync::future::FenceSignalFuture;
use vulkano::sync::GpuFuture;
use crate::runtime::broad_phase::{pack, unpack, BroadPairs, BroadProxy};
mod shader {
vulkano_shaders::shader! {
ty: "compute",
path: "src/shaders/compute/broad_phase.comp",
}
}
struct Pending {
fence: FenceSignalFuture<Box<dyn GpuFuture>>,
buffer: Subbuffer<[u32]>,
tick: u64,
}
pub struct GpuBroadPhase {
device: Arc<Device>,
queue: Arc<Queue>,
pipeline: Arc<ComputePipeline>,
memory: Arc<StandardMemoryAllocator>,
commands: Arc<StandardCommandBufferAllocator>,
descriptors: Arc<StandardDescriptorSetAllocator>,
pending: Vec<Pending>,
}
impl GpuBroadPhase {
pub fn new(device: Arc<Device>, queue: Arc<Queue>) -> Result<Self, String> {
let error = |error: &dyn std::fmt::Display| error.to_string();
let entry_point = shader::load(device.clone())
.map_err(|e| error(&e))?
.entry_point("main")
.ok_or("broad_phase.comp has no main")?;
let stage = PipelineShaderStageCreateInfo::new(entry_point);
let layout = PipelineLayout::new(
device.clone(),
PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage])
.into_pipeline_layout_create_info(device.clone())
.map_err(|e| format!("{e:?}"))?,
)
.map_err(|e| error(&e))?;
let pipeline = ComputePipeline::new(
device.clone(),
None,
ComputePipelineCreateInfo::stage_layout(stage, layout),
)
.map_err(|e| error(&e))?;
Ok(Self {
memory: Arc::new(StandardMemoryAllocator::new_default(
device.clone(),
)),
commands: Arc::new(StandardCommandBufferAllocator::new(
device.clone(),
Default::default(),
)),
descriptors: Arc::new(StandardDescriptorSetAllocator::new(
device.clone(),
Default::default(),
)),
device,
queue,
pipeline,
pending: Vec::new(),
})
}
pub fn submit(
&mut self,
tick: u64,
proxies: &[BroadProxy],
capacity: u32,
) -> Result<bool, String> {
let Some(words) = pack(proxies, capacity) else {
return Ok(false);
};
let error = |error: &dyn std::fmt::Display| error.to_string();
let buffer = Buffer::from_iter(
self.memory.clone(),
BufferCreateInfo {
usage: BufferUsage::STORAGE_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_RANDOM_ACCESS,
..Default::default()
},
words,
)
.map_err(|e| format!("{e:?}"))?;
let set = DescriptorSet::new(
self.descriptors.clone(),
self.pipeline.layout().set_layouts()[0].clone(),
[WriteDescriptorSet::buffer(0, buffer.clone())],
[],
)
.map_err(|e| format!("{e:?}"))?;
let mut builder = AutoCommandBufferBuilder::primary(
self.commands.clone(),
self.queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)
.map_err(|e| format!("{e:?}"))?;
builder
.bind_pipeline_compute(self.pipeline.clone())
.map_err(|e| format!("{e:?}"))?
.bind_descriptor_sets(
PipelineBindPoint::Compute,
self.pipeline.layout().clone(),
0,
set,
)
.map_err(|e| format!("{e:?}"))?;
unsafe { builder.dispatch([1, 1, 1]) }.map_err(|e| format!("{e:?}"))?;
let command_buffer = builder.build().map_err(|e| format!("{e:?}"))?;
let fence = vulkano::sync::now(self.device.clone())
.then_execute(self.queue.clone(), command_buffer)
.map_err(|e| error(&e))?
.boxed()
.then_signal_fence_and_flush()
.map_err(|e| format!("{e:?}"))?;
self.pending.push(Pending {
fence,
buffer,
tick,
});
Ok(true)
}
#[must_use]
pub fn in_flight(&self) -> usize {
self.pending.len()
}
pub fn take_completed(&mut self) -> Vec<(u64, BroadPairs)> {
let mut done = Vec::new();
let mut index = 0;
while index < self.pending.len() {
if !self.pending[index].fence.is_signaled().unwrap_or(false) {
index += 1;
continue;
}
let pending = self.pending.remove(index);
if pending.fence.wait(Some(Duration::ZERO)).is_err() {
continue;
}
if let Ok(words) = pending.buffer.read() {
done.push((pending.tick, unpack(&words)));
};
}
done
}
pub fn block_until_complete(&mut self) {
for pending in &self.pending {
let _ = pending.fence.wait(None);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::broad_phase::pairs_on_cpu;
use crate::runtime::broad_phase::tests::scattered;
#[test]
#[cfg_attr(
not(feature = "gpu-tests"),
ignore = "run with `--features gpu-tests` on a machine with a Vulkan driver"
)]
fn the_gpu_broad_phase_finds_the_reference_pairs() {
use crate::rendering::test_support::headless_device;
if vulkano::VulkanLibrary::new().is_err() {
eprintln!("skipping: no Vulkan driver present");
return;
}
let base = headless_device();
let mut broad =
GpuBroadPhase::new(base.device.clone(), base.queue.clone())
.unwrap();
let mut pairs = |proxies: &[BroadProxy], capacity| {
if !broad.submit(7, proxies, capacity).unwrap() {
return None;
}
broad.block_until_complete();
let mut done = broad.take_completed();
assert_eq!(done.len(), 1);
let (tick, pairs) = done.pop().unwrap();
assert_eq!(tick, 7);
Some(pairs)
};
let proxies = scattered(3000);
let expected = pairs_on_cpu(&proxies, 256);
assert!(expected.pairs.len() > 1000, "{}", expected.pairs.len());
assert!(expected.overflowed.is_empty());
let first = pairs(&proxies, 256).unwrap();
assert_eq!(first, expected);
let tight = pairs_on_cpu(&proxies, 2);
assert!(!tight.overflowed.is_empty());
for _ in 0..3 {
assert_eq!(pairs(&proxies, 2).unwrap(), tight);
}
let points: Vec<_> = proxies
.iter()
.map(|p| BroadProxy { max: p.min, ..*p })
.collect();
assert_eq!(pairs(&points, 4).unwrap(), pairs_on_cpu(&points, 4));
assert_eq!(pairs(&[], 4).unwrap(), BroadPairs::default());
let bad = [BroadProxy {
min: [1.0; 3],
max: [0.0; 3],
layer: 1,
mask: 1,
}];
assert_eq!(pairs(&bad, 4), None);
assert!(broad.submit(1, &proxies, 256).unwrap());
assert!(broad.submit(2, &points, 4).unwrap());
assert_eq!(broad.in_flight(), 2);
broad.block_until_complete();
let done = broad.take_completed();
assert_eq!(broad.in_flight(), 0);
assert_eq!(done, vec![(1, expected), (2, pairs_on_cpu(&points, 4))]);
assert!(broad.take_completed().is_empty());
}
}