use std::{borrow::Cow, marker::PhantomData, time::Duration};
use bevy::{
platform::collections::HashMap, prelude::{AssetServer, World}, render::{
render_resource::{
encase::{private::WriteInto, StorageBuffer, UniformBuffer},
Buffer, ComputePipelineDescriptor, ShaderRef, ShaderType,
},
renderer::RenderDevice,
}
};
use wgpu::{util::BufferInitDescriptor, BufferDescriptor, BufferUsages};
use crate::{
pipeline_cache::{AppPipelineCache, CachedAppComputePipelineId},
traits::{ComputeShader, ComputeWorker},
worker::{AppComputeWorker, ComputePass, RunMode, StagingBuffer, Step},
};
pub struct AppComputeWorkerBuilder<'a, W: ComputeWorker> {
pub(crate) world: &'a mut World,
pub(crate) cached_pipeline_ids: HashMap<String, CachedAppComputePipelineId>,
pub(crate) buffers: HashMap<String, Buffer>,
pub(crate) staging_buffers: HashMap<String, StagingBuffer>,
pub(crate) steps: Vec<Step>,
pub(crate) run_mode: RunMode,
pub(crate) maximum_async_time: Option<Duration>,
extra_buffer_usages: Option<BufferUsages>,
_phantom: PhantomData<W>,
}
impl<'a, W: ComputeWorker> AppComputeWorkerBuilder<'a, W> {
pub fn new(world: &'a mut World) -> Self {
Self {
world,
cached_pipeline_ids: HashMap::default(),
buffers: HashMap::default(),
staging_buffers: HashMap::default(),
steps: vec![],
run_mode: RunMode::Continuous,
maximum_async_time: Some(Duration::from_secs(0)),
extra_buffer_usages: None,
_phantom: PhantomData,
}
}
pub fn add_uniform<T: ShaderType + WriteInto>(&mut self, name: &str, uniform: &T) -> &mut Self {
T::assert_uniform_compat();
let mut buffer = UniformBuffer::new(Vec::new());
buffer.write::<T>(uniform).unwrap();
let render_device = self.world.resource::<RenderDevice>();
let mut usage = BufferUsages::COPY_DST | BufferUsages::UNIFORM;
if let Some(extra_usages) = self.extra_buffer_usages {
usage |= extra_usages;
}
self.buffers.insert(
name.to_owned(),
render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some(name),
contents: buffer.as_ref(),
usage,
}),
);
self
}
pub fn add_storage<T: ShaderType + WriteInto>(&mut self, name: &str, storage: &T) -> &mut Self {
let mut buffer = StorageBuffer::new(Vec::new());
buffer.write::<T>(storage).unwrap();
let render_device = self.world.resource::<RenderDevice>();
let mut usage = BufferUsages::COPY_DST | BufferUsages::STORAGE;
if let Some(extra_usages) = self.extra_buffer_usages {
usage |= extra_usages;
}
self.buffers.insert(
name.to_owned(),
render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some(name),
contents: buffer.as_ref(),
usage,
}),
);
self
}
pub fn add_rw_storage<T: ShaderType + WriteInto>(
&mut self,
name: &str,
storage: &T,
) -> &mut Self {
let mut buffer = StorageBuffer::new(Vec::new());
buffer.write::<T>(storage).unwrap();
let render_device = self.world.resource::<RenderDevice>();
let mut usage = BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE;
if let Some(extra_usages) = self.extra_buffer_usages {
usage |= extra_usages;
}
self.buffers.insert(
name.to_owned(),
render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some(name),
contents: buffer.as_ref(),
usage,
}),
);
self
}
pub fn add_staging<T: ShaderType + WriteInto>(&mut self, name: &str, data: &T) -> &mut Self {
self.add_rw_storage(name, data);
let buffer = self.buffers.get(name).unwrap();
let render_device = self.world.resource::<RenderDevice>();
let staging = StagingBuffer {
mapped: true,
buffer: render_device.create_buffer(&BufferDescriptor {
label: Some(name),
size: buffer.size(),
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
mapped_at_creation: true,
}),
};
self.staging_buffers.insert(name.to_owned(), staging);
self
}
pub fn add_empty_uniform(&mut self, name: &str, size: u64) -> &mut Self {
let render_device = self.world.resource::<RenderDevice>();
let mut usage = BufferUsages::COPY_DST | BufferUsages::UNIFORM;
if let Some(extra_usages) = self.extra_buffer_usages {
usage |= extra_usages;
}
self.buffers.insert(
name.to_owned(),
render_device.create_buffer(&BufferDescriptor {
label: Some(name),
size,
usage,
mapped_at_creation: false,
}),
);
self
}
pub fn add_empty_storage(&mut self, name: &str, size: u64) -> &mut Self {
let render_device = self.world.resource::<RenderDevice>();
let mut usage = BufferUsages::COPY_DST | BufferUsages::STORAGE;
if let Some(extra_usages) = self.extra_buffer_usages {
usage |= extra_usages;
}
self.buffers.insert(
name.to_owned(),
render_device.create_buffer(&BufferDescriptor {
label: Some(name),
size,
usage,
mapped_at_creation: false,
}),
);
self
}
pub fn add_empty_rw_storage(&mut self, name: &str, size: u64) -> &mut Self {
let render_device = self.world.resource::<RenderDevice>();
let mut usage = BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE;
if let Some(extra_usages) = self.extra_buffer_usages {
usage |= extra_usages;
}
self.buffers.insert(
name.to_owned(),
render_device.create_buffer(&BufferDescriptor {
label: Some(name),
size,
usage,
mapped_at_creation: false,
}),
);
self
}
pub fn add_empty_staging(&mut self, name: &str, size: u64) -> &mut Self {
self.add_empty_rw_storage(name, size);
let buffer = self.buffers.get(name).unwrap();
let render_device = self.world.resource::<RenderDevice>();
let staging = StagingBuffer {
mapped: true,
buffer: render_device.create_buffer(&BufferDescriptor {
label: Some(name),
size: buffer.size(),
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
mapped_at_creation: true,
}),
};
self.staging_buffers.insert(name.to_owned(), staging);
self
}
pub fn add_pass<S: ComputeShader>(&mut self, workgroups: [u32; 3], vars: &[&str]) -> &mut Self {
if !self.cached_pipeline_ids.contains_key(S::type_path()) {
let pipeline_cache = self.world.resource::<AppPipelineCache>();
let asset_server = self.world.resource::<AssetServer>();
let shader = match S::shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
}
.unwrap();
let cached_id = pipeline_cache.queue_app_compute_pipeline(ComputePipelineDescriptor {
label: None,
layout: S::layouts().to_vec(),
push_constant_ranges: S::push_constant_ranges().to_vec(),
shader_defs: S::shader_defs().to_vec(),
entry_point: Cow::Borrowed(S::entry_point()),
shader,
zero_initialize_workgroup_memory: false,
});
self.cached_pipeline_ids
.insert(S::type_path().to_string(), cached_id);
}
self.steps.push(Step::ComputePass(ComputePass {
workgroups,
vars: vars.iter().map(|a| String::from(*a)).collect(),
shader_type_path: S::type_path().to_string(),
}));
self
}
pub fn add_swap(&mut self, buffer_a: &str, buffer_b: &str) -> &mut Self {
self.steps
.push(Step::Swap(buffer_a.to_owned(), buffer_b.to_owned()));
self
}
pub fn set_extra_buffer_usages(&mut self, usages: Option<BufferUsages>) -> &mut Self {
self.extra_buffer_usages = usages;
self
}
pub fn continuous(&mut self) -> &mut Self {
self.run_mode = RunMode::Continuous;
self
}
pub fn one_shot(&mut self) -> &mut Self {
self.run_mode = RunMode::OneShot(false);
self
}
pub fn synchronous(&mut self) -> &mut Self {
self.maximum_async_time = Some(Duration::from_secs(0));
self
}
pub fn asynchronous(&mut self, maximum_async_time: Option<Duration>) -> &mut Self {
self.maximum_async_time = maximum_async_time;
self
}
pub fn build(&self) -> AppComputeWorker<W> {
AppComputeWorker::from(self)
}
}