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: &'a [wgpu::VertexBufferLayout<'a>],
89    pub entries: &'a [MaterialBindingEntry],
90    pub cull_mode: Option<wgpu::Face>,
91    pub depth: Option<wgpu::DepthStencilState>,
92    pub targets: &'a [wgpu::ColorTargetState],
93    pub polygon_mode: wgpu::PolygonMode,
94    pub extra_layouts: &'a [&'a wgpu::BindGroupLayout],
95}
96
97const 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: &[],
111            entries: &[],
112            cull_mode: Some(wgpu::Face::Back),
113            depth: None,
114            targets: &[],
115            extra_layouts: &[],
116            polygon_mode: wgpu::PolygonMode::Fill,
117        }
118    }
119}
120
121pub fn build_material(
122    device: &wgpu::Device,
123    desc: &MaterialDescriptor,
124) -> (wgpu::RenderPipeline, wgpu::BindGroupLayout) {
125    let layout_entries: Vec<_> = desc
126        .entries
127        .iter()
128        .enumerate()
129        .map(|(i, e)| e.kind.layout_entry(i as u32))
130        .collect();
131
132    let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
133        label: desc.label,
134        entries: &layout_entries,
135    });
136
137    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
138        label: desc.label,
139        source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
140    });
141
142    let mut bind_group_layouts: Vec<_> = desc.extra_layouts.iter().map(|l| Some(*l)).collect();
143    bind_group_layouts.push(Some(&layout));
144
145    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
146        label: desc.label,
147        bind_group_layouts: &bind_group_layouts,
148        immediate_size: 0,
149    });
150
151    let targets: Vec<_> = desc.targets.iter().cloned().map(Some).collect();
152
153    let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
154        label: desc.label,
155        layout: Some(&pipeline_layout),
156        vertex: wgpu::VertexState {
157            module: &module,
158            entry_point: desc.vertex_entry,
159            compilation_options: Default::default(),
160            buffers: desc.vertex_layouts,
161        },
162        primitive: wgpu::PrimitiveState {
163            topology: wgpu::PrimitiveTopology::TriangleList,
164            strip_index_format: None,
165            front_face: wgpu::FrontFace::Ccw,
166            cull_mode: desc.cull_mode,
167            unclipped_depth: false,
168            polygon_mode: desc.polygon_mode,
169            conservative: false,
170        },
171        depth_stencil: desc.depth.clone(),
172        multisample: wgpu::MultisampleState::default(),
173        fragment: Some(wgpu::FragmentState {
174            module: &module,
175            entry_point: desc.fragment_entry,
176            compilation_options: Default::default(),
177            targets: &targets,
178        }),
179        multiview_mask: None,
180        cache: None,
181    });
182
183    (pipeline, layout)
184}
185
186pub struct GPUMaterial {
187    pub pipeline: wgpu::RenderPipeline,
188    pub layout: wgpu::BindGroupLayout,
189    pub entries: Vec<MaterialBindingEntry>,
190}
191
192impl Asset<WGPUBackend> for GPUMaterial {
193    type Source = MaterialDescriptor<'static>;
194    type Deps<'a> = ();
195
196    fn upload<'a>(source: &MaterialDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
197        let (pipeline, layout) = build_material(&backend.device, &source);
198
199        Some(Self {
200            pipeline,
201            layout,
202            entries: source.entries.to_vec(),
203        })
204    }
205}
206
207pub struct MaterialPlugin;
208impl MaterialPlugin {
209    pub fn new() -> Self {
210        Self
211    }
212}
213
214impl Plugin for MaterialPlugin {
215    fn build(&self, app: &mut App) {
216        app.add_plugin(AssetPlugin::<super::backend::WGPUBackend, GPUMaterial>::new());
217    }
218}