1use crate::{
2 app::App,
3 assets::{plugin::AssetPlugin, upload::Asset},
4 ecs::plugin::Plugin,
5 wgpu::backend::WGPUBackend,
6};
7
8#[derive(Copy, Clone, PartialEq, Eq, Hash)]
9pub enum ComputeBindingKind {
10 StorageBufferReadOnly {
11 has_dynamic_offset: bool,
12 min_binding_size: Option<wgpu::BufferSize>,
13 },
14 StorageBufferReadWrite {
15 has_dynamic_offset: bool,
16 min_binding_size: Option<wgpu::BufferSize>,
17 },
18 UniformBuffer {
19 has_dynamic_offset: bool,
20 min_binding_size: Option<wgpu::BufferSize>,
21 },
22 Texture {
23 sample_type: wgpu::TextureSampleType,
24 view_dimension: wgpu::TextureViewDimension,
25 multisampled: bool,
26 },
27 StorageTexture {
28 format: wgpu::TextureFormat,
29 access: wgpu::StorageTextureAccess,
30 view_dimension: wgpu::TextureViewDimension,
31 },
32 Sampler,
33 ComparisonSampler,
34}
35
36impl ComputeBindingKind {
37 pub fn texture_2d() -> Self {
38 Self::Texture {
39 sample_type: wgpu::TextureSampleType::Float { filterable: true },
40 view_dimension: wgpu::TextureViewDimension::D2,
41 multisampled: false,
42 }
43 }
44
45 pub fn storage_buffer_read_only() -> Self {
46 Self::StorageBufferReadOnly { has_dynamic_offset: false, min_binding_size: None }
47 }
48
49 pub fn storage_buffer_read_write() -> Self {
50 Self::StorageBufferReadWrite { has_dynamic_offset: false, min_binding_size: None }
51 }
52
53 pub fn uniform_buffer() -> Self {
54 Self::UniformBuffer { has_dynamic_offset: false, min_binding_size: None }
55 }
56
57 pub fn dynamic_uniform_buffer(element_size: u64) -> Self {
67 Self::UniformBuffer { has_dynamic_offset: true, min_binding_size: wgpu::BufferSize::new(element_size) }
68 }
69
70 pub fn dynamic_storage_buffer(element_size: u64, read_only: bool) -> Self {
72 let has_dynamic_offset = true;
73 let min_binding_size = wgpu::BufferSize::new(element_size);
74 if read_only {
75 Self::StorageBufferReadOnly { has_dynamic_offset, min_binding_size }
76 } else {
77 Self::StorageBufferReadWrite { has_dynamic_offset, min_binding_size }
78 }
79 }
80
81 pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
82 match self {
83 ComputeBindingKind::StorageBufferReadOnly { has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
84 binding,
85 visibility: wgpu::ShaderStages::COMPUTE,
86 ty: wgpu::BindingType::Buffer {
87 ty: wgpu::BufferBindingType::Storage { read_only: true },
88 has_dynamic_offset: *has_dynamic_offset,
89 min_binding_size: *min_binding_size,
90 },
91 count: None,
92 },
93 ComputeBindingKind::StorageBufferReadWrite { has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
94 binding,
95 visibility: wgpu::ShaderStages::COMPUTE,
96 ty: wgpu::BindingType::Buffer {
97 ty: wgpu::BufferBindingType::Storage { read_only: false },
98 has_dynamic_offset: *has_dynamic_offset,
99 min_binding_size: *min_binding_size,
100 },
101 count: None,
102 },
103 ComputeBindingKind::UniformBuffer { has_dynamic_offset, min_binding_size } => wgpu::BindGroupLayoutEntry {
104 binding,
105 visibility: wgpu::ShaderStages::COMPUTE,
106 ty: wgpu::BindingType::Buffer {
107 ty: wgpu::BufferBindingType::Uniform,
108 has_dynamic_offset: *has_dynamic_offset,
109 min_binding_size: *min_binding_size,
110 },
111 count: None,
112 },
113 ComputeBindingKind::Texture { sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
114 binding,
115 visibility: wgpu::ShaderStages::COMPUTE,
116 ty: wgpu::BindingType::Texture {
117 sample_type: *sample_type,
118 view_dimension: *view_dimension,
119 multisampled: *multisampled,
120 },
121 count: None,
122 },
123 ComputeBindingKind::StorageTexture { format, access, view_dimension } => wgpu::BindGroupLayoutEntry {
124 binding,
125 visibility: wgpu::ShaderStages::COMPUTE,
126 ty: wgpu::BindingType::StorageTexture {
127 access: *access,
128 format: *format,
129 view_dimension: *view_dimension,
130 },
131 count: None,
132 },
133 ComputeBindingKind::Sampler => wgpu::BindGroupLayoutEntry {
134 binding,
135 visibility: wgpu::ShaderStages::COMPUTE,
136 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
137 count: None,
138 },
139 ComputeBindingKind::ComparisonSampler => wgpu::BindGroupLayoutEntry {
140 binding,
141 visibility: wgpu::ShaderStages::COMPUTE,
142 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
143 count: None,
144 },
145 }
146 }
147}
148
149#[derive(Clone)]
150pub struct ComputeBindingEntry {
151 pub name: &'static str,
152 pub binding: u32,
155 pub kind: ComputeBindingKind,
156}
157
158pub struct ComputeDescriptor<'a> {
159 pub label: Option<&'a str>,
160 pub shader_source: &'a str,
161 pub entry_point: Option<&'a str>,
162 pub entries: Vec<ComputeBindingEntry>,
163 pub own_group: Option<u32>,
166 pub extra_layouts: Vec<super::layout::OwnedGroupLayout>,
171}
172
173impl<'a> Default for ComputeDescriptor<'a> {
174 fn default() -> Self {
175 Self {
176 label: None,
177 shader_source: "",
178 entry_point: Some("cs_main"),
179 entries: Vec::new(),
180 own_group: Some(0),
181 extra_layouts: Vec::new(),
182 }
183 }
184}
185
186pub fn build_bind_group_layout(
187 device: &wgpu::Device,
188 label: Option<&str>,
189 entries: &[ComputeBindingEntry],
190) -> wgpu::BindGroupLayout {
191 let layout_entries: Vec<_> = entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
192
193 let mut seen = std::collections::HashSet::new();
194 for e in entries {
195 if !seen.insert(e.binding) {
196 panic!(
197 "binding {} assigned more than once building bind group layout{} (entry '{}')",
198 e.binding,
199 label.map(|l| format!(" '{l}'")).unwrap_or_default(),
200 e.name
201 );
202 }
203 }
204
205 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
206 label,
207 entries: &layout_entries,
208 })
209}
210
211pub fn build_compute(
212 device: &wgpu::Device,
213 desc: &ComputeDescriptor,
214) -> (wgpu::ComputePipeline, wgpu::BindGroupLayout) {
215 let layout = build_bind_group_layout(device, desc.label, &desc.entries);
216
217 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
218 label: desc.label,
219 source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
220 });
221
222 let mut slots: Vec<super::layout::GroupLayout> = desc
223 .extra_layouts
224 .iter()
225 .map(|g| super::layout::GroupLayout { group: g.group, layout: &g.layout })
226 .collect();
227 if let Some(own_group) = desc.own_group {
228 slots.push(super::layout::GroupLayout { group: own_group, layout: &layout });
229 }
230 let bind_group_layouts = super::layout::assemble_bind_group_layouts(desc.label, slots);
231
232 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
233 label: desc.label,
234 bind_group_layouts: &bind_group_layouts,
235 immediate_size: 0,
236 });
237
238 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
239 label: desc.label,
240 layout: Some(&pipeline_layout),
241 module: &module,
242 entry_point: desc.entry_point,
243 compilation_options: Default::default(),
244 cache: None,
245 });
246
247 (pipeline, layout)
248}
249
250pub struct GPUCompute {
251 pub pipeline: wgpu::ComputePipeline,
252 pub layout: wgpu::BindGroupLayout,
253 pub entries: Vec<ComputeBindingEntry>,
254}
255
256impl Asset<WGPUBackend> for GPUCompute {
257 type Source = ComputeDescriptor<'static>;
258 type Deps<'a> = ();
259
260 fn upload<'a>(source: &ComputeDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
261 let (pipeline, layout) = build_compute(&backend.device, source);
262
263 Some(Self {
264 pipeline,
265 layout,
266 entries: source.entries.to_vec(),
267 })
268 }
269}
270
271#[derive(Default)]
272pub struct ComputePlugin;
273impl ComputePlugin {
274 pub fn new() -> Self {
275 Self
276 }
277}
278
279impl Plugin for ComputePlugin {
280 fn build(&self, app: &mut App) {
281 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUCompute>::new());
282 }
283}