#![cfg(feature = "gpu")]
use std::collections::HashMap;
use crate::engine::error::{ECSError, ECSResult, ExecutionError};
use crate::engine::types::SystemID;
use crate::gpu::GPUBindingDesc;
use crate::gpu::GPUContext;
type PipelineKey = (SystemID, u64, u64, usize, usize, usize, u64);
type PipelineEntry = (
wgpu::ComputePipeline,
wgpu::BindGroupLayout,
Option<wgpu::BindGroupLayout>,
);
type PipelineRefs<'a> = (
&'a wgpu::ComputePipeline,
&'a wgpu::BindGroupLayout,
Option<&'a wgpu::BindGroupLayout>,
);
#[inline]
pub(crate) fn hash_str(s: &str) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut h);
h.finish()
}
#[inline]
fn hash_resource_layout(descriptions: &[GPUBindingDesc]) -> u64 {
let mut hash: u64 = 1469598103934665603;
for description in descriptions {
hash ^= description.key() as u64;
hash = hash.wrapping_mul(1099511628211);
}
hash
}
#[derive(Debug)]
pub struct PipelineCache {
map: HashMap<PipelineKey, PipelineEntry>,
}
impl PipelineCache {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn get_or_create(
&mut self,
context: &GPUContext,
system_id: SystemID,
shader_wgsl: &'static str,
entry_point: &'static str,
read_count: usize,
write_count: usize,
resource_layout: &[GPUBindingDesc],
) -> ECSResult<PipelineRefs<'_>> {
let shader_hash = hash_str(shader_wgsl);
let entry_hash = hash_str(entry_point);
let group1_len = resource_layout.len();
let group1_sig = hash_resource_layout(resource_layout);
let key = (
system_id,
shader_hash,
entry_hash,
read_count,
write_count,
group1_len,
group1_sig,
);
if let std::collections::hash_map::Entry::Vacant(e) = self.map.entry(key) {
let (pipeline, bgl0, bgl1) = create_pipeline(
context,
shader_wgsl,
entry_point,
read_count,
write_count,
resource_layout,
)
.map_err(|e| ECSError::from(ExecutionError::GpuDispatchFailed { message: e.into() }))?;
e.insert((pipeline, bgl0, bgl1));
}
let (pipeline, bgl0, bgl1) = self.map.get(&key).unwrap();
Ok((pipeline, bgl0, bgl1.as_ref()))
}
}
fn create_pipeline(
context: &GPUContext,
shader_wgsl: &'static str,
entry_point: &'static str,
read_count: usize,
write_count: usize,
resource_layout: &[GPUBindingDesc],
) -> Result<
(
wgpu::ComputePipeline,
wgpu::BindGroupLayout,
Option<wgpu::BindGroupLayout>,
),
String,
> {
let resource_count = resource_layout.len();
let mut entries0 = Vec::with_capacity(read_count + write_count + 1);
for i in 0..read_count {
entries0.push(wgpu::BindGroupLayoutEntry {
binding: i as u32,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
}
for j in 0..write_count {
let binding = (read_count + j) as u32;
entries0.push(wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
}
let params_binding = (read_count + write_count) as u32;
entries0.push(wgpu::BindGroupLayoutEntry {
binding: params_binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
let bgl0 = context
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("abm_bgl_group0"),
entries: &entries0,
});
let bgl1 = if resource_count > 0 {
let mut entries1 = Vec::with_capacity(resource_count);
for (k, desc) in resource_layout.iter().enumerate() {
entries1.push(wgpu::BindGroupLayoutEntry {
binding: k as u32,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage {
read_only: desc.read_only,
},
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
});
}
Some(
context
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("abm_bgl_group1"),
entries: &entries1,
}),
)
} else {
None
};
let mut layouts: Vec<Option<&wgpu::BindGroupLayout>> = vec![Some(&bgl0)];
if let Some(ref b) = bgl1 {
layouts.push(Some(b));
}
let pl = context
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("abm_pipeline_layout"),
bind_group_layouts: &layouts,
immediate_size: 0,
});
let module = context
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("abm_shader"),
source: wgpu::ShaderSource::Wgsl(shader_wgsl.into()),
});
let pipeline = context
.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("abm_compute_pipeline"),
layout: Some(&pl),
module: &module,
entry_point: Some(entry_point),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
Ok((pipeline, bgl0, bgl1))
}