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