use std::borrow::Cow;
use bevy::render::render_resource::encase::private::WriteInto;
use bevy::{
asset::AssetPath,
prelude::*,
render::{
diagnostic::RecordDiagnostics,
render_resource::*,
renderer::{RenderContext, RenderDevice, RenderQueue},
},
};
#[derive(Clone, Debug)]
pub enum ShaderSource {
Path(&'static str),
Handle(Handle<Shader>),
}
#[derive(Debug, Clone)]
pub struct PassSpec {
pub key: &'static str,
pub shader: ShaderSource,
pub entry_points: &'static [&'static str],
pub layout: BindGroupLayoutDescriptor,
}
#[derive(Resource, Debug)]
pub struct Passes {
specs: Vec<PassSpec>,
ids: Vec<Vec<CachedComputePipelineId>>,
}
impl Passes {
pub fn new(asset_server: &AssetServer, cache: &PipelineCache, specs: Vec<PassSpec>) -> Self {
let shader_handles: Vec<Handle<Shader>> = specs
.iter()
.map(|spec| match &spec.shader {
ShaderSource::Path(path) => asset_server.load(AssetPath::parse(path)),
ShaderSource::Handle(handle) => handle.clone(),
})
.collect();
let ids = specs
.iter()
.zip(shader_handles.iter().cloned())
.map(|(spec, shader)| {
spec.entry_points
.iter()
.map(|entry| {
cache.queue_compute_pipeline(ComputePipelineDescriptor {
label: Some(Cow::Owned(format!("{}::{entry}", spec.key))),
layout: vec![spec.layout.clone()],
shader: shader.clone(),
entry_point: Some(Cow::Borrowed(entry)),
..default()
})
})
.collect()
})
.collect();
Self { specs, ids }
}
fn locate(&self, key: &str, entry: &str) -> Option<CachedComputePipelineId> {
let (spec, ids) = self
.specs
.iter()
.zip(&self.ids)
.find(|(spec, _)| spec.key == key)?;
spec.entry_points
.iter()
.copied()
.zip(ids)
.find(|(point, _)| *point == entry)
.map(|(_, id)| *id)
}
pub fn layout(&self, key: &str) -> Option<&BindGroupLayoutDescriptor> {
self.specs
.iter()
.find(|spec| spec.key == key)
.map(|spec| &spec.layout)
}
pub fn ready<'c>(
&self,
cache: &'c PipelineCache,
key: &str,
entry: &str,
) -> Option<&'c ComputePipeline> {
let id = self.locate(key, entry)?;
match cache.get_compute_pipeline_state(id) {
CachedPipelineState::Ok(_) => cache.get_compute_pipeline(id),
CachedPipelineState::Err(_)
| CachedPipelineState::Queued
| CachedPipelineState::Creating(_) => None,
}
}
pub fn ready_all<'c>(
&self,
cache: &'c PipelineCache,
passes: &[(&'static str, &'static str)],
) -> Option<Ready<'c>> {
let pipelines = passes
.iter()
.map(|&(key, entry)| self.ready(cache, key, entry))
.collect::<Option<Vec<_>>>()?;
Some(Ready {
keys: passes.to_vec(),
pipelines,
})
}
}
#[derive(Debug)]
pub struct Ready<'c> {
keys: Vec<(&'static str, &'static str)>,
pipelines: Vec<&'c ComputePipeline>,
}
impl<'c> Ready<'c> {
pub fn get(&self, key: &str, entry: &str) -> &'c ComputePipeline {
let index = self
.keys
.iter()
.position(|&(k, e)| k == key && e == entry)
.expect("requested pass was part of ready_all");
self.pipelines[index]
}
}
#[derive(Resource, Default, Debug)]
pub struct Groups {
groups: Vec<(&'static str, BindGroup)>,
}
impl Groups {
pub fn created(&self) -> bool {
!self.groups.is_empty()
}
pub fn get(&self, key: &str) -> Option<&BindGroup> {
self.groups
.iter()
.find(|(name, _)| *name == key)
.map(|(_, group)| group)
}
pub fn register(&mut self, key: &'static str, group: BindGroup) {
if let Some((_, existing)) = self.groups.iter_mut().find(|(name, _)| *name == key) {
*existing = group;
} else {
self.groups.push((key, group));
}
}
}
pub fn bind_group<const N: usize>(
device: &RenderDevice,
cache: &PipelineCache,
passes: &Passes,
pass_key: &str,
label: &'static str,
entries: &BindGroupEntries<'_, N>,
) -> BindGroup {
let layout = passes
.layout(pass_key)
.unwrap_or_else(|| panic!("unknown pass {pass_key}"));
let layout = cache.get_bind_group_layout(layout);
device.create_bind_group(Some(label), &layout, entries)
}
pub fn write_uniform<T: ShaderType + WriteInto + Clone>(
slot: &mut Option<UniformBuffer<T>>,
value: T,
device: &RenderDevice,
queue: &RenderQueue,
) {
let uniform = slot.get_or_insert_with(|| UniformBuffer::from(value.clone()));
uniform.set(value);
uniform.write_buffer(device, queue);
}
#[derive(Debug)]
pub enum Step<'a> {
Dispatch {
pipeline: &'a ComputePipeline,
group: &'a BindGroup,
workgroups: [u32; 3],
},
CopyTexture {
source: &'a Texture,
target: &'a Texture,
extent: Extent3d,
},
}
#[derive(Debug)]
pub struct Span<'a> {
pub label: &'static str,
pub steps: Vec<Step<'a>>,
}
impl<'a> Span<'a> {
pub fn new(label: &'static str, steps: Vec<Step<'a>>) -> Self {
Self { label, steps }
}
}
pub fn run_spans(context: &mut RenderContext, spans: &[Span]) {
let recorder = context.diagnostic_recorder();
let diagnostics = recorder.as_deref();
for span in spans {
let timed = diagnostics.time_span(context.command_encoder(), span.label);
for step in &span.steps {
match *step {
Step::Dispatch {
pipeline,
group,
workgroups,
} => {
let descriptor = ComputePassDescriptor {
label: Some(span.label),
..default()
};
let mut pass = context.command_encoder().begin_compute_pass(&descriptor);
pass.set_bind_group(0, group, &[]);
pass.set_pipeline(pipeline);
let [x, y, z] = workgroups;
pass.dispatch_workgroups(x, y, z);
}
Step::CopyTexture {
source,
target,
extent,
} => {
context.command_encoder().copy_texture_to_texture(
TexelCopyTextureInfo {
texture: source,
mip_level: 0,
origin: Origin3d::default(),
aspect: TextureAspect::All,
},
TexelCopyTextureInfo {
texture: target,
mip_level: 0,
origin: Origin3d::default(),
aspect: TextureAspect::All,
},
extent,
);
}
}
}
timed.end(context.command_encoder());
}
}