Skip to main content

renderling/material/
cpu.rs

1//! CPU side of materials.
2use std::sync::{Arc, Mutex};
3
4use craballoc::{
5    // Craballoc is used for memory allocation and management.
6    runtime::WgpuRuntime,
7    slab::{SlabAllocator, SlabBuffer},
8    value::Hybrid,
9};
10use crabslab::Id;
11use glam::{Vec3, Vec4};
12
13use crate::{
14    atlas::{Atlas, AtlasTexture},
15    material::shader::MaterialDescriptor,
16};
17
18/// Wrapper around the materials slab, which holds material textures in an
19/// atlas.
20#[derive(Clone)]
21pub struct Materials {
22    slab: SlabAllocator<WgpuRuntime>,
23    atlas: Atlas,
24    default_material: Material,
25}
26
27impl AsRef<WgpuRuntime> for Materials {
28    fn as_ref(&self) -> &WgpuRuntime {
29        self.slab.runtime()
30    }
31}
32
33impl Materials {
34    /// Creates a new `Materials` instance with the specified runtime and atlas
35    /// size.
36    ///
37    /// # Arguments
38    ///
39    /// * `runtime` - A reference to the WgpuRuntime.
40    /// * `atlas_size` - The size of the atlas texture.
41    pub fn new(runtime: impl AsRef<WgpuRuntime>, atlas_size: wgpu::Extent3d) -> Self {
42        let slab = SlabAllocator::new(runtime, "materials", wgpu::BufferUsages::empty());
43        let atlas = Atlas::new(&slab, atlas_size, None, Some("materials-atlas"), None);
44        let default_material = Material {
45            descriptor: slab.new_value(Default::default()),
46            albedo_texture: Default::default(),
47            metallic_roughness_texture: Default::default(),
48            normal_mapping_texture: Default::default(),
49            ao_texture: Default::default(),
50            emissive_texture: Default::default(),
51        };
52        Self {
53            slab,
54            atlas,
55            default_material,
56        }
57    }
58
59    /// Returns a reference to the WgpuRuntime.
60    pub fn runtime(&self) -> &WgpuRuntime {
61        self.as_ref()
62    }
63
64    /// Returns a reference to the slab allocator.
65    pub fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
66        &self.slab
67    }
68
69    /// Returns a reference to the atlas.
70    pub fn atlas(&self) -> &Atlas {
71        &self.atlas
72    }
73
74    /// Returns the default material.
75    pub fn default_material(&self) -> &Material {
76        &self.default_material
77    }
78
79    /// Runs atlas upkeep and commits all changes to the GPU.
80    ///
81    /// Returns `true` if the atlas texture was recreated.
82    #[must_use]
83    pub fn commit(&self) -> (bool, SlabBuffer<wgpu::Buffer>) {
84        // Atlas upkeep must be called first because it generates updates into
85        // the slab
86        (self.atlas.upkeep(self.runtime()), self.slab.commit())
87    }
88
89    /// Stage a new [`Material`] on the materials slab.
90    pub fn new_material(&self) -> Material {
91        let descriptor = self.slab.new_value(MaterialDescriptor::default());
92        Material {
93            descriptor,
94            albedo_texture: Default::default(),
95            metallic_roughness_texture: Default::default(),
96            normal_mapping_texture: Default::default(),
97            ao_texture: Default::default(),
98            emissive_texture: Default::default(),
99        }
100    }
101}
102
103/// A material staged on the GPU.
104///
105/// Internally a `Material` holds references to:
106/// * its descriptor, [`MaterialDescriptor`], which lives on the GPU
107/// * [`AtlasTexture`]s that determine how the material presents:
108///   * albedo color
109///   * metallic roughness
110///   * normal mapping
111///   * ambient occlusion
112///   * emissive
113///
114/// ## Note
115///
116/// Clones of `Material` all point to the same underlying data.
117/// Changing a value on one `Material` will change that value for all clones as
118/// well.
119#[derive(Clone)]
120pub struct Material {
121    descriptor: Hybrid<MaterialDescriptor>,
122
123    albedo_texture: Arc<Mutex<Option<AtlasTexture>>>,
124    metallic_roughness_texture: Arc<Mutex<Option<AtlasTexture>>>,
125    normal_mapping_texture: Arc<Mutex<Option<AtlasTexture>>>,
126    ao_texture: Arc<Mutex<Option<AtlasTexture>>>,
127    emissive_texture: Arc<Mutex<Option<AtlasTexture>>>,
128}
129
130impl From<&Material> for Material {
131    fn from(value: &Material) -> Self {
132        value.clone()
133    }
134}
135
136impl Material {
137    /// Returns the unique identifier of the material descriptor.
138    pub fn id(&self) -> Id<MaterialDescriptor> {
139        self.descriptor.id()
140    }
141
142    /// Returns a copy of the underlying descriptor.
143    pub fn descriptor(&self) -> MaterialDescriptor {
144        self.descriptor.get()
145    }
146
147    /// Sets the emissive factor of the material.
148    ///
149    /// # Arguments
150    ///
151    /// * `param` - The emissive factor as a `Vec3`.
152    pub fn set_emissive_factor(&self, param: Vec3) -> &Self {
153        self.descriptor.modify(|d| d.emissive_factor = param);
154        self
155    }
156    /// Sets the emissive factor.
157    ///
158    /// # Arguments
159    ///
160    /// * `param` - The emissive factor as a `Vec3`.
161    pub fn with_emissive_factor(self, param: Vec3) -> Self {
162        self.set_emissive_factor(param);
163        self
164    }
165    /// Sets the emissive strength multiplier of the material.
166    ///
167    /// # Arguments
168    ///
169    /// * `param` - The emissive strength multiplier as a `f32`.
170    pub fn set_emissive_strength_multiplier(&self, param: f32) -> &Self {
171        self.descriptor
172            .modify(|d| d.emissive_strength_multiplier = param);
173        self
174    }
175    /// Sets the emissive strength multiplier.
176    ///
177    /// # Arguments
178    ///
179    /// * `param` - The emissive strength multiplier as a `f32`.
180    pub fn with_emissive_strength_multiplier(self, param: f32) -> Self {
181        self.set_emissive_strength_multiplier(param);
182        self
183    }
184    /// Sets the albedo factor of the material.
185    ///
186    /// # Arguments
187    ///
188    /// * `param` - The albedo factor as a `Vec4`.
189    pub fn set_albedo_factor(&self, param: Vec4) -> &Self {
190        self.descriptor.modify(|d| d.albedo_factor = param);
191        self
192    }
193    /// Sets the albedo factor.
194    ///
195    /// # Arguments
196    ///
197    /// * `param` - The albedo factor as a `Vec4`.
198    pub fn with_albedo_factor(self, param: Vec4) -> Self {
199        self.set_albedo_factor(param);
200        self
201    }
202    /// Sets the metallic factor of the material.
203    ///
204    /// # Arguments
205    ///
206    /// * `param` - The metallic factor as a `f32`.
207    pub fn set_metallic_factor(&self, param: f32) -> &Self {
208        self.descriptor.modify(|d| d.metallic_factor = param);
209        self
210    }
211    /// Sets the metallic factor.
212    ///
213    /// # Arguments
214    ///
215    /// * `param` - The metallic factor as a `f32`.
216    pub fn with_metallic_factor(self, param: f32) -> Self {
217        self.set_metallic_factor(param);
218        self
219    }
220    /// Sets the roughness factor of the material.
221    ///
222    /// # Arguments
223    ///
224    /// * `param` - The roughness factor as a `f32`.
225    pub fn set_roughness_factor(&self, param: f32) -> &Self {
226        self.descriptor.modify(|d| d.roughness_factor = param);
227        self
228    }
229    /// Sets the roughness factor.
230    ///
231    /// # Arguments
232    ///
233    /// * `param` - The roughness factor as a `f32`.
234    pub fn with_roughness_factor(self, param: f32) -> Self {
235        self.set_roughness_factor(param);
236        self
237    }
238    /// Sets the albedo texture coordinate of the material.
239    ///
240    /// # Arguments
241    ///
242    /// * `param` - The texture coordinate as a `u32`.
243    pub fn set_albedo_tex_coord(&self, param: u32) -> &Self {
244        self.descriptor.modify(|d| d.albedo_tex_coord = param);
245        self
246    }
247    /// Sets the albedo texture coordinate.
248    ///
249    /// # Arguments
250    ///
251    /// * `param` - The texture coordinate as a `u32`.
252    pub fn with_albedo_tex_coord(self, param: u32) -> Self {
253        self.set_albedo_tex_coord(param);
254        self
255    }
256    /// Sets the metallic roughness texture coordinate of the material.
257    ///
258    /// # Arguments
259    ///
260    /// * `param` - The texture coordinate as a `u32`.
261    pub fn set_metallic_roughness_tex_coord(&self, param: u32) -> &Self {
262        self.descriptor
263            .modify(|d| d.metallic_roughness_tex_coord = param);
264        self
265    }
266    /// Sets the metallic roughness texture coordinate.
267    ///
268    /// # Arguments
269    ///
270    /// * `param` - The texture coordinate as a `u32`.
271    pub fn with_metallic_roughness_tex_coord(self, param: u32) -> Self {
272        self.set_metallic_roughness_tex_coord(param);
273        self
274    }
275    /// Sets the normal texture coordinate of the material.
276    ///
277    /// # Arguments
278    ///
279    /// * `param` - The texture coordinate as a `u32`.
280    pub fn set_normal_tex_coord(&self, param: u32) -> &Self {
281        self.descriptor.modify(|d| d.normal_tex_coord = param);
282        self
283    }
284    /// Sets the normal texture coordinate.
285    ///
286    /// # Arguments
287    ///
288    /// * `param` - The texture coordinate as a `u32`.
289    pub fn with_normal_tex_coord(self, param: u32) -> Self {
290        self.set_normal_tex_coord(param);
291        self
292    }
293    /// Sets the ambient occlusion texture coordinate of the material.
294    ///
295    /// # Arguments
296    ///
297    /// * `param` - The texture coordinate as a `u32`.
298    pub fn set_ambient_occlusion_tex_coord(&self, param: u32) -> &Self {
299        self.descriptor.modify(|d| d.ao_tex_coord = param);
300        self
301    }
302    /// Sets the ambient occlusion texture coordinate.
303    ///
304    /// # Arguments
305    ///
306    /// * `param` - The texture coordinate as a `u32`.
307    pub fn with_ambient_occlusion_tex_coord(self, param: u32) -> Self {
308        self.set_ambient_occlusion_tex_coord(param);
309        self
310    }
311    /// Sets the emissive texture coordinate of the material.
312    ///
313    /// # Arguments
314    ///
315    /// * `param` - The texture coordinate as a `u32`.
316    pub fn set_emissive_tex_coord(&self, param: u32) -> &Self {
317        self.descriptor.modify(|d| d.emissive_tex_coord = param);
318        self
319    }
320    /// Sets the emissive texture coordinate.
321    ///
322    /// # Arguments
323    ///
324    /// * `param` - The texture coordinate as a `u32`.
325    pub fn with_emissive_tex_coord(self, param: u32) -> Self {
326        self.set_emissive_tex_coord(param);
327        self
328    }
329    /// Sets whether the material has lighting.
330    ///
331    /// # Arguments
332    ///
333    /// * `param` - A boolean indicating if the material has lighting.
334    pub fn set_has_lighting(&self, param: bool) -> &Self {
335        self.descriptor.modify(|d| d.has_lighting = param);
336        self
337    }
338    /// Sets whether the material has lighting.
339    ///
340    /// # Arguments
341    ///
342    /// * `param` - A boolean indicating if the material has lighting.
343    pub fn with_has_lighting(self, param: bool) -> Self {
344        self.set_has_lighting(param);
345        self
346    }
347    /// Sets the ambient occlusion strength of the material.
348    ///
349    /// # Arguments
350    ///
351    /// * `param` - The ambient occlusion strength as a `f32`.
352    pub fn set_ambient_occlusion_strength(&self, param: f32) -> &Self {
353        self.descriptor.modify(|d| d.ao_strength = param);
354        self
355    }
356    /// Sets the ambient occlusion strength.
357    ///
358    /// # Arguments
359    ///
360    /// * `param` - The ambient occlusion strength as a `f32`.
361    pub fn with_ambient_occlusion_strength(self, param: f32) -> Self {
362        self.set_ambient_occlusion_strength(param);
363        self
364    }
365
366    /// Remove the albedo texture.
367    ///
368    /// This causes any `[Primitive]` that references this material to fall back
369    /// to using the albedo factor for color.
370    pub fn remove_albedo_texture(&self) {
371        self.descriptor.modify(|d| d.albedo_texture_id = Id::NONE);
372        self.albedo_texture
373            .lock()
374            .expect("albedo_texture lock")
375            .take();
376    }
377
378    /// Sets the albedo color texture.
379    pub fn set_albedo_texture(&self, texture: &AtlasTexture) -> &Self {
380        self.descriptor
381            .modify(|d| d.albedo_texture_id = texture.id());
382        *self.albedo_texture.lock().expect("albedo_texture lock") = Some(texture.clone());
383        self
384    }
385
386    /// Replace the albedo texture.
387    pub fn with_albedo_texture(self, texture: &AtlasTexture) -> Self {
388        self.descriptor
389            .modify(|d| d.albedo_texture_id = texture.id());
390        *self.albedo_texture.lock().expect("albedo_texture lock") = Some(texture.clone());
391        self
392    }
393
394    /// Remove the metallic roughness texture.
395    ///
396    /// This causes any `[Renderlet]` that references this material to fall back
397    /// to using the metallic and roughness factors for appearance.
398    pub fn remove_metallic_roughness_texture(&self) {
399        self.descriptor
400            .modify(|d| d.metallic_roughness_texture_id = Id::NONE);
401        self.metallic_roughness_texture
402            .lock()
403            .expect("metallic_roughness_texture lock")
404            .take();
405    }
406
407    /// Sets the metallic roughness texture of the material.
408    ///
409    /// # Arguments
410    ///
411    /// * `texture` - A reference to the metallic roughness `AtlasTexture`.
412    pub fn set_metallic_roughness_texture(&self, texture: &AtlasTexture) -> &Self {
413        self.descriptor
414            .modify(|d| d.metallic_roughness_texture_id = texture.id());
415        *self
416            .metallic_roughness_texture
417            .lock()
418            .expect("metallic_roughness_texture lock") = Some(texture.clone());
419        self
420    }
421
422    /// Sets the metallic roughness texture and returns the material.
423    ///
424    /// # Arguments
425    ///
426    /// * `texture` - A reference to the metallic roughness `AtlasTexture`.
427    pub fn with_metallic_roughness_texture(self, texture: &AtlasTexture) -> Self {
428        self.set_metallic_roughness_texture(texture);
429        self
430    }
431
432    /// Remove the normal texture.
433    ///
434    /// This causes any `[Renderlet]` that references this material to fall back
435    /// to using the default normal mapping.
436    pub fn remove_normal_texture(&self) {
437        self.descriptor.modify(|d| d.normal_texture_id = Id::NONE);
438        self.normal_mapping_texture
439            .lock()
440            .expect("normal_mapping_texture lock")
441            .take();
442    }
443
444    /// Sets the normal texture of the material.
445    ///
446    /// # Arguments
447    ///
448    /// * `texture` - A reference to the normal `AtlasTexture`.
449    pub fn set_normal_texture(&self, texture: &AtlasTexture) -> &Self {
450        self.descriptor
451            .modify(|d| d.normal_texture_id = texture.id());
452        *self
453            .normal_mapping_texture
454            .lock()
455            .expect("normal_mapping_texture lock") = Some(texture.clone());
456        self
457    }
458
459    /// Sets the normal texture and returns the material.
460    ///
461    /// # Arguments
462    ///
463    /// * `texture` - A reference to the normal `AtlasTexture`.
464    pub fn with_normal_texture(self, texture: &AtlasTexture) -> Self {
465        self.set_normal_texture(texture);
466        self
467    }
468
469    /// Remove the ambient occlusion texture.
470    ///
471    /// This causes any `[Renderlet]` that references this material to fall back
472    /// to using the default ambient occlusion.
473    pub fn remove_ambient_occlusion_texture(&self) {
474        self.descriptor.modify(|d| d.ao_texture_id = Id::NONE);
475        self.ao_texture.lock().expect("ao_texture lock").take();
476    }
477
478    /// Sets the ambient occlusion texture of the material.
479    ///
480    /// # Arguments
481    ///
482    /// * `texture` - A reference to the ambient occlusion `AtlasTexture`.
483    pub fn set_ambient_occlusion_texture(&self, texture: &AtlasTexture) -> &Self {
484        self.descriptor.modify(|d| d.ao_texture_id = texture.id());
485        *self.ao_texture.lock().expect("ao_texture lock") = Some(texture.clone());
486        self
487    }
488
489    /// Sets the ambient occlusion texture and returns the material.
490    ///
491    /// # Arguments
492    ///
493    /// * `texture` - A reference to the ambient occlusion `AtlasTexture`.
494    pub fn with_ambient_occlusion_texture(self, texture: &AtlasTexture) -> Self {
495        self.set_ambient_occlusion_texture(texture);
496        self
497    }
498
499    /// Remove the emissive texture.
500    ///
501    /// This causes any `[Renderlet]` that references this material to fall back
502    /// to using the emissive factor for appearance.
503    pub fn remove_emissive_texture(&self) {
504        self.descriptor.modify(|d| d.emissive_texture_id = Id::NONE);
505        self.emissive_texture
506            .lock()
507            .expect("emissive_texture lock")
508            .take();
509    }
510
511    /// Sets the emissive texture of the material.
512    ///
513    /// # Arguments
514    ///
515    /// * `texture` - A reference to the emissive `AtlasTexture`.
516    pub fn set_emissive_texture(&self, texture: &AtlasTexture) -> &Self {
517        self.descriptor
518            .modify(|d| d.emissive_texture_id = texture.id());
519        *self.emissive_texture.lock().expect("emissive_texture lock") = Some(texture.clone());
520        self
521    }
522
523    /// Sets the emissive texture and returns the material.
524    ///
525    /// # Arguments
526    ///
527    /// * `texture` - A reference to the emissive `AtlasTexture`.
528    pub fn with_emissive_texture(self, texture: &AtlasTexture) -> Self {
529        self.set_emissive_texture(texture);
530        self
531    }
532}