1use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9
10use wde_wgpu::bind_group::{BindGroup, BindGroupBuilder, BindGroupLayout, WgpuBindGroupLayout};
11use wde_wgpu::buffer::{Buffer, BufferBindingType, BufferUsage};
12use wde_wgpu::command_buffer::CommandBuffer;
13use wde_wgpu::compute_pipeline::ComputePipeline;
14use wde_wgpu::instance::RenderInstanceData;
15use wde_wgpu::render_pipeline::ShaderStages;
16
17use crate::core::RenderInstance;
18
19struct CachedPipeline {
20 pipeline: ComputePipeline,
21 layout: WgpuBindGroupLayout
22}
23
24struct BufferSet {
26 params: Buffer,
27 output: Buffer,
28 inputs: Vec<Buffer>,
29 staging: Buffer,
30 bind_group: BindGroup
31}
32
33#[derive(PartialEq, Eq, Hash, Clone)]
35struct BufferShape {
36 label: &'static str,
37 params_size: usize,
38 output_len: usize,
39 input_lens: Vec<usize>
40}
41
42pub struct ComputeDispatcher {
45 instance: RenderInstance,
46 pipelines: Mutex<HashMap<&'static str, Arc<CachedPipeline>>>,
47 buffer_pool: Mutex<HashMap<BufferShape, Vec<BufferSet>>>
48}
49impl ComputeDispatcher {
50 pub fn new(instance: &RenderInstance) -> Self {
51 Self {
52 instance: RenderInstance(instance.0.clone()),
53 pipelines: Mutex::new(HashMap::new()),
54 buffer_pool: Mutex::new(HashMap::new())
55 }
56 }
57
58 pub fn dispatch_f32<P: bytemuck::NoUninit>(
63 &self,
64 label: &'static str,
65 shader: &str,
66 params: &P,
67 inputs: &[&[f32]],
68 output_len: usize,
69 workgroups: (u32, u32, u32)
70 ) -> Result<Vec<f32>, String> {
71 let data = self
72 .instance
73 .0
74 .read()
75 .map_err(|_| "render instance lock poisoned".to_string())?;
76
77 let pipeline = self.pipeline_for(label, shader, inputs.len(), &data)?;
78 let shape = BufferShape {
79 label,
80 params_size: std::mem::size_of::<P>(),
81 output_len,
82 input_lens: inputs.iter().map(|slice| slice.len()).collect()
83 };
84 let buffers = self.checkout_buffers(shape.clone(), &pipeline, &data)?;
85
86 buffers.params.write(&data, bytemuck::bytes_of(params), 0);
87 for (buf, input) in buffers.inputs.iter().zip(inputs) {
88 buf.write(&data, bytemuck::cast_slice(input), 0);
89 }
90
91 let mut cmd = CommandBuffer::new(&data, label);
92 {
93 let mut pass = cmd.create_compute_pass(label);
94 pass.set_pipeline(&pipeline.pipeline)
95 .map_err(|e| format!("{label}: {e:?}"))?
96 .set_bind_group(0, &buffers.bind_group);
97 pass.dispatch(workgroups.0, workgroups.1, workgroups.2)
98 .map_err(|e| format!("{label}: {e:?}"))?;
99 }
100 cmd.submit(&data);
101
102 buffers.staging.copy_from_buffer(&data, &buffers.output);
103 let mut result = vec![0.0f32; output_len];
104 buffers.staging.map_read(&data, |view| {
105 result.copy_from_slice(bytemuck::cast_slice(&view));
106 });
107
108 self.buffer_pool
109 .lock()
110 .unwrap()
111 .entry(shape)
112 .or_default()
113 .push(buffers);
114 Ok(result)
115 }
116
117 fn pipeline_for(
118 &self,
119 label: &'static str,
120 shader: &str,
121 input_count: usize,
122 data: &RenderInstanceData
123 ) -> Result<Arc<CachedPipeline>, String> {
124 let mut cache = self.pipelines.lock().unwrap();
125 if let Some(cached) = cache.get(label) {
126 return Ok(cached.clone());
127 }
128
129 let layout = BindGroupLayout::new(label, |b| {
130 b.add_buffer(0, ShaderStages::COMPUTE, BufferBindingType::Uniform);
131 b.add_buffer(
132 1,
133 ShaderStages::COMPUTE,
134 BufferBindingType::Storage { read_only: false }
135 );
136 for i in 0..input_count {
137 b.add_buffer(
138 2 + i as u32,
139 ShaderStages::COMPUTE,
140 BufferBindingType::Storage { read_only: true }
141 );
142 }
143 });
144 let wgpu_layout = layout.build(data).map_err(|e| format!("{label}: {e:?}"))?;
145 let mut pipeline = ComputePipeline::new(label);
146 pipeline
147 .set_shader(shader)
148 .set_bind_groups(vec![wgpu_layout.clone()]);
149 pipeline.init(data).map_err(|e| format!("{label}: {e:?}"))?;
150
151 let cached = Arc::new(CachedPipeline {
152 pipeline,
153 layout: wgpu_layout
154 });
155 cache.insert(label, cached.clone());
156 Ok(cached)
157 }
158
159 fn checkout_buffers(
161 &self,
162 shape: BufferShape,
163 pipeline: &CachedPipeline,
164 data: &RenderInstanceData
165 ) -> Result<BufferSet, String> {
166 if let Some(set) = self
167 .buffer_pool
168 .lock()
169 .unwrap()
170 .get_mut(&shape)
171 .and_then(Vec::pop)
172 {
173 return Ok(set);
174 }
175
176 let label = shape.label;
177 let params = Buffer::new(
178 data,
179 &format!("{label}-params"),
180 shape.params_size,
181 BufferUsage::UNIFORM | BufferUsage::COPY_DST,
182 None
183 );
184 let output_size = shape.output_len * std::mem::size_of::<f32>();
185 let output = Buffer::new(
186 data,
187 &format!("{label}-output"),
188 output_size,
189 BufferUsage::STORAGE | BufferUsage::COPY_SRC,
190 None
191 );
192 let inputs: Vec<Buffer> = shape
193 .input_lens
194 .iter()
195 .enumerate()
196 .map(|(i, &len)| {
197 Buffer::new(
198 data,
199 &format!("{label}-input{i}"),
200 len * std::mem::size_of::<f32>(),
201 BufferUsage::STORAGE | BufferUsage::COPY_DST,
202 None
203 )
204 })
205 .collect();
206 let staging = Buffer::new(
207 data,
208 &format!("{label}-staging"),
209 output_size,
210 BufferUsage::MAP_READ | BufferUsage::COPY_DST,
211 None
212 );
213
214 let mut entries = vec![
215 BindGroupBuilder::buffer(0, ¶ms),
216 BindGroupBuilder::buffer(1, &output),
217 ];
218 for (i, buf) in inputs.iter().enumerate() {
219 entries.push(BindGroupBuilder::buffer(2 + i as u32, buf));
220 }
221 let bind_group = BindGroupBuilder::build(label, data, &pipeline.layout, &entries)
222 .map_err(|e| format!("{label}: {e:?}"))?;
223
224 Ok(BufferSet {
225 params,
226 output,
227 inputs,
228 staging,
229 bind_group
230 })
231 }
232}