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: u32,
165 pub extra_layouts: Vec<super::layout::OwnedGroupLayout>,
170}
171
172impl<'a> Default for ComputeDescriptor<'a> {
173 fn default() -> Self {
174 Self {
175 label: None,
176 shader_source: "",
177 entry_point: Some("cs_main"),
178 entries: Vec::new(),
179 own_group: 0,
180 extra_layouts: Vec::new(),
181 }
182 }
183}
184
185pub fn build_bind_group_layout(
186 device: &wgpu::Device,
187 label: Option<&str>,
188 entries: &[ComputeBindingEntry],
189) -> wgpu::BindGroupLayout {
190 let layout_entries: Vec<_> = entries.iter().map(|e| e.kind.layout_entry(e.binding)).collect();
191
192 let mut seen = std::collections::HashSet::new();
193 for e in entries {
194 if !seen.insert(e.binding) {
195 panic!(
196 "binding {} assigned more than once building bind group layout{} (entry '{}')",
197 e.binding,
198 label.map(|l| format!(" '{l}'")).unwrap_or_default(),
199 e.name
200 );
201 }
202 }
203
204 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
205 label,
206 entries: &layout_entries,
207 })
208}
209
210pub fn build_compute(
211 device: &wgpu::Device,
212 desc: &ComputeDescriptor,
213) -> (wgpu::ComputePipeline, wgpu::BindGroupLayout) {
214 let layout = build_bind_group_layout(device, desc.label, &desc.entries);
215
216 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
217 label: desc.label,
218 source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
219 });
220
221 let mut slots: Vec<super::layout::GroupLayout> = desc
222 .extra_layouts
223 .iter()
224 .map(|g| super::layout::GroupLayout { group: g.group, layout: &g.layout })
225 .collect();
226 slots.push(super::layout::GroupLayout { group: desc.own_group, layout: &layout });
227 let bind_group_layouts = super::layout::assemble_bind_group_layouts(desc.label, slots);
228
229 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
230 label: desc.label,
231 bind_group_layouts: &bind_group_layouts,
232 immediate_size: 0,
233 });
234
235 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
236 label: desc.label,
237 layout: Some(&pipeline_layout),
238 module: &module,
239 entry_point: desc.entry_point,
240 compilation_options: Default::default(),
241 cache: None,
242 });
243
244 (pipeline, layout)
245}
246
247pub struct GPUCompute {
248 pub pipeline: wgpu::ComputePipeline,
249 pub layout: wgpu::BindGroupLayout,
250 pub entries: Vec<ComputeBindingEntry>,
251}
252
253impl Asset<WGPUBackend> for GPUCompute {
254 type Source = ComputeDescriptor<'static>;
255 type Deps<'a> = ();
256
257 fn upload<'a>(source: &ComputeDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
258 let (pipeline, layout) = build_compute(&backend.device, source);
259
260 Some(Self {
261 pipeline,
262 layout,
263 entries: source.entries.to_vec(),
264 })
265 }
266}
267
268#[derive(Default)]
269pub struct ComputePlugin;
270impl ComputePlugin {
271 pub fn new() -> Self {
272 Self
273 }
274}
275
276impl Plugin for ComputePlugin {
277 fn build(&self, app: &mut App) {
278 app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUCompute>::new());
279 }
280}