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