Skip to main content

pebble/wgpu/
material.rs

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 MaterialBindingKind {
10    Texture {
11        sample_type: wgpu::TextureSampleType,
12        view_dimension: wgpu::TextureViewDimension,
13        multisampled: bool,
14    },
15    Sampler,
16    ComparisonSampler,
17    UniformBuffer { visibility: wgpu::ShaderStages },
18    StorageBufferReadOnly { visibility: wgpu::ShaderStages },
19    StorageBufferReadWrite { visibility: wgpu::ShaderStages },
20}
21
22impl MaterialBindingKind {
23    pub fn texture_2d() -> Self {
24        Self::Texture {
25            sample_type: wgpu::TextureSampleType::Float { filterable: true },
26            view_dimension: wgpu::TextureViewDimension::D2,
27            multisampled: false,
28        }
29    }
30
31    pub fn texture_2d_array() -> Self {
32        Self::Texture {
33            sample_type: wgpu::TextureSampleType::Float { filterable: true },
34            view_dimension: wgpu::TextureViewDimension::D2Array,
35            multisampled: false,
36        }
37    }
38
39    pub fn texture_cubemap() -> Self {
40        Self::Texture {
41            sample_type: wgpu::TextureSampleType::Float { filterable: true },
42            view_dimension: wgpu::TextureViewDimension::Cube,
43            multisampled: false,
44        }
45    }
46
47    pub fn uniform_buffer() -> Self {
48        Self::UniformBuffer { visibility: wgpu::ShaderStages::VERTEX_FRAGMENT }
49    }
50
51    pub fn layout_entry(&self, binding: u32) -> wgpu::BindGroupLayoutEntry {
52        match self {
53            MaterialBindingKind::Texture { sample_type, view_dimension, multisampled } => wgpu::BindGroupLayoutEntry {
54                binding,
55                visibility: wgpu::ShaderStages::FRAGMENT,
56                ty: wgpu::BindingType::Texture {
57                    sample_type: *sample_type,
58                    view_dimension: *view_dimension,
59                    multisampled: *multisampled,
60                },
61                count: None,
62            },
63            MaterialBindingKind::Sampler => wgpu::BindGroupLayoutEntry {
64                binding,
65                visibility: wgpu::ShaderStages::FRAGMENT,
66                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
67                count: None,
68            },
69            MaterialBindingKind::ComparisonSampler => wgpu::BindGroupLayoutEntry {
70                binding,
71                visibility: wgpu::ShaderStages::FRAGMENT,
72                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
73                count: None,
74            },
75            MaterialBindingKind::UniformBuffer { visibility } => wgpu::BindGroupLayoutEntry {
76                binding,
77                visibility: *visibility,
78                ty: wgpu::BindingType::Buffer {
79                    ty: wgpu::BufferBindingType::Uniform,
80                    has_dynamic_offset: false,
81                    min_binding_size: None,
82                },
83                count: None,
84            },
85            MaterialBindingKind::StorageBufferReadOnly { visibility } => wgpu::BindGroupLayoutEntry {
86                binding,
87                visibility: *visibility,
88                ty: wgpu::BindingType::Buffer {
89                    ty: wgpu::BufferBindingType::Storage { read_only: true },
90                    has_dynamic_offset: false,
91                    min_binding_size: None,
92                },
93                count: None,
94            },
95            MaterialBindingKind::StorageBufferReadWrite { visibility } => wgpu::BindGroupLayoutEntry {
96                binding,
97                visibility: *visibility,
98                ty: wgpu::BindingType::Buffer {
99                    ty: wgpu::BufferBindingType::Storage { read_only: false },
100                    has_dynamic_offset: false,
101                    min_binding_size: None,
102                },
103                count: None,
104            },
105        }
106    }
107}
108
109#[derive(Clone)]
110pub struct MaterialBindingEntry {
111    pub name: &'static str,
112    pub kind: MaterialBindingKind,
113}
114
115pub struct MaterialDescriptor<'a> {
116    pub label: Option<&'a str>,
117    pub shader_source: &'a str,
118    pub vertex_entry: Option<&'a str>,
119    pub fragment_entry: Option<&'a str>,
120    pub vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
121    pub entries: Vec<MaterialBindingEntry>,
122    pub cull_mode: Option<wgpu::Face>,
123    pub depth: Option<wgpu::DepthStencilState>,
124    pub targets: Vec<wgpu::ColorTargetState>,
125    pub polygon_mode: wgpu::PolygonMode,
126    pub extra_layouts: Vec<wgpu::BindGroupLayout>,
127}
128
129pub const DEFAULT_TARGET: [wgpu::ColorTargetState; 1] = [wgpu::ColorTargetState {
130    format: wgpu::TextureFormat::Rgba8Unorm,
131    blend: None,
132    write_mask: wgpu::ColorWrites::ALL,
133}];
134
135impl<'a> Default for MaterialDescriptor<'a> {
136    fn default() -> Self {
137        Self {
138            label: None,
139            shader_source: "",
140            vertex_entry: Some("vs_main"),
141            fragment_entry: Some("fs_main"),
142            vertex_layouts: Vec::new(),
143            entries: Vec::new(),
144            cull_mode: Some(wgpu::Face::Back),
145            depth: None,
146            targets: Vec::new(),
147            extra_layouts: Vec::new(),
148            polygon_mode: wgpu::PolygonMode::Fill,
149        }
150    }
151}
152
153pub fn build_bind_group_layout(
154    device: &wgpu::Device,
155    label: Option<&str>,
156    entries: &[MaterialBindingEntry],
157) -> wgpu::BindGroupLayout {
158    let layout_entries: Vec<_> = entries
159        .iter()
160        .enumerate()
161        .map(|(i, e)| e.kind.layout_entry(i as u32))
162        .collect();
163
164    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
165        label,
166        entries: &layout_entries,
167    })
168}
169
170pub fn build_material(
171    device: &wgpu::Device,
172    desc: &MaterialDescriptor,
173) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout) {
174    let layout = build_bind_group_layout(device, desc.label, &desc.entries);
175
176    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
177        label: desc.label,
178        source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
179    });
180
181    let mut bind_group_layouts: Vec<&wgpu::BindGroupLayout> = desc.extra_layouts.iter().collect();
182    bind_group_layouts.push(&layout);
183    let bind_group_layouts: Vec<Option<&wgpu::BindGroupLayout>> =
184        bind_group_layouts.into_iter().map(Some).collect();
185
186    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
187        label: desc.label,
188        bind_group_layouts: &bind_group_layouts,
189        immediate_size: 0,
190    });
191
192    let targets: Vec<Option<wgpu::ColorTargetState>> =
193        desc.targets.iter().cloned().map(Some).collect();
194
195    let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
196        label: desc.label,
197        layout: Some(&pipeline_layout),
198        vertex: wgpu::VertexState {
199            module: &module,
200            entry_point: desc.vertex_entry,
201            compilation_options: Default::default(),
202            buffers: &desc.vertex_layouts,
203        },
204        primitive: wgpu::PrimitiveState {
205            topology: wgpu::PrimitiveTopology::TriangleList,
206            strip_index_format: None,
207            front_face: wgpu::FrontFace::Ccw,
208            cull_mode: desc.cull_mode,
209            unclipped_depth: false,
210            polygon_mode: desc.polygon_mode,
211            conservative: false,
212        },
213        depth_stencil: desc.depth.clone(),
214        multisample: wgpu::MultisampleState::default(),
215        fragment: Some(wgpu::FragmentState {
216            module: &module,
217            entry_point: desc.fragment_entry,
218            compilation_options: Default::default(),
219            targets: &targets,
220        }),
221        multiview_mask: None,
222        cache: None,
223    });
224
225    (pipeline, layout)
226}
227
228pub struct GPUMaterial {
229    pub pipeline: wgpu::RenderPipeline,
230    pub layout: wgpu::BindGroupLayout,
231    pub entries: Vec<MaterialBindingEntry>,
232}
233
234impl Asset<WGPUBackend> for GPUMaterial {
235    type Source = MaterialDescriptor<'static>;
236    type Deps<'a> = ();
237
238    fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
239        let (pipeline, layout) = build_material(&backend.device, source);
240
241        Some(Self {
242            pipeline,
243            layout,
244            entries: source.entries.to_vec(),
245        })
246    }
247}
248
249#[derive(Default)]
250pub struct MaterialPlugin;
251impl MaterialPlugin {
252    pub fn new() -> Self {
253        Self
254    }
255}
256
257impl Plugin for MaterialPlugin {
258    fn build(&self, app: &mut App) {
259        app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
260    }
261}