fyrox_impl/material/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Material is a set of parameters for a shader. This module contains everything related to materials.
22//!
23//! See [Material struct docs](self::Material) for more info.
24
25#![warn(missing_docs)]
26
27use crate::{
28    asset::{
29        io::ResourceIo,
30        manager::{BuiltInResource, ResourceManager},
31        state::ResourceState,
32        untyped::ResourceKind,
33        Resource, ResourceData,
34    },
35    core::{
36        algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
37        color::Color,
38        io::FileLoadError,
39        parking_lot::Mutex,
40        reflect::prelude::*,
41        sstorage::ImmutableString,
42        uuid::{uuid, Uuid},
43        visitor::{prelude::*, RegionGuard},
44        TypeUuidProvider,
45    },
46    material::shader::{SamplerFallback, ShaderResource, ShaderResourceExtension},
47    resource::texture::TextureResource,
48};
49use fxhash::FxHashMap;
50use fyrox_core::Downcast;
51use lazy_static::lazy_static;
52use std::{
53    error::Error,
54    fmt::{Display, Formatter},
55    path::Path,
56    sync::Arc,
57};
58use strum_macros::{AsRefStr, EnumString, VariantNames};
59
60pub mod loader;
61pub mod shader;
62
63/// A texture binding.
64#[derive(Default, Debug, Visit, Clone, Reflect, TypeUuidProvider)]
65#[type_uuid(id = "e1642a47-d372-4840-a8eb-f16350f436f8")]
66pub struct MaterialTextureBinding {
67    /// Actual value of the texture binding. Could be [`None`], in this case fallback value of the
68    /// shader will be used.
69    pub value: Option<TextureResource>,
70}
71
72/// A value of a resource binding that will be used for rendering.
73///
74/// # Limitations
75///
76/// There is a limited set of possible types that can be passed to a shader, most of them are
77/// just simple data types.
78#[derive(Debug, Visit, Clone, Reflect, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
79#[type_uuid(id = "2df8f1e5-0075-4d0d-9860-70fc27d3e165")]
80pub enum MaterialResourceBinding {
81    /// A texture.
82    Texture(MaterialTextureBinding),
83    /// A group of properties.
84    PropertyGroup(MaterialPropertyGroup),
85}
86
87impl Default for MaterialResourceBinding {
88    fn default() -> Self {
89        Self::PropertyGroup(Default::default())
90    }
91}
92
93impl MaterialResourceBinding {
94    /// Tries to extract a texture from the resource binding.
95    pub fn as_texture(&self) -> Option<TextureResource> {
96        if let Self::Texture(binding) = self {
97            binding.value.clone()
98        } else {
99            None
100        }
101    }
102}
103
104/// Property group stores a bunch of named values of a fixed set of types, that will be used for
105/// rendering with some shader.
106#[derive(Default, Debug, Visit, Clone, Reflect)]
107pub struct MaterialPropertyGroup {
108    properties: FxHashMap<ImmutableString, MaterialProperty>,
109}
110
111impl MaterialPropertyGroup {
112    /// Searches for a property with given name.
113    ///
114    /// # Complexity
115    ///
116    /// O(1)
117    ///
118    /// # Examples
119    ///
120    /// ```no_run
121    /// # use fyrox_impl::core::sstorage::ImmutableString;
122    /// # use fyrox_impl::material::MaterialPropertyGroup;
123    ///
124    /// let mut group = MaterialPropertyGroup::default();
125    /// let color = group.property_ref("diffuseColor").unwrap().as_color();
126    /// ```
127    pub fn property_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialProperty> {
128        let name = name.into();
129        self.properties.get(&name)
130    }
131
132    /// Searches for a property with given name.
133    ///
134    /// # Complexity
135    ///
136    /// O(1)
137    ///
138    /// # Examples
139    ///
140    /// ```no_run
141    /// # use fyrox_core::color::Color;
142    /// # use fyrox_impl::core::sstorage::ImmutableString;
143    /// # use fyrox_impl::material::{MaterialProperty, MaterialPropertyGroup};
144    ///
145    /// let mut group = MaterialPropertyGroup::default();
146    /// *group.property_mut("diffuseColor").unwrap() = MaterialProperty::Color(Color::RED);
147    /// ```
148    pub fn property_mut(
149        &mut self,
150        name: impl Into<ImmutableString>,
151    ) -> Option<&mut MaterialProperty> {
152        let name = name.into();
153        self.properties.get_mut(&name)
154    }
155
156    /// Sets new value of the property with given name.
157    ///
158    /// # Type checking
159    ///
160    /// This method does not check if the property exists in the shader nor its type. Validation
161    /// happens in the renderer, when it tries to use the property group. This is made for performance
162    /// reasons.
163    ///
164    /// # Example
165    ///
166    /// ```no_run
167    /// # use fyrox_impl::material::{Material, MaterialPropertyGroup};
168    /// # use fyrox_impl::core::color::Color;
169    ///
170    /// let mut group = MaterialPropertyGroup::default();
171    /// group.set_property("diffuseColor", Color::WHITE);
172    /// ```
173    pub fn set_property(
174        &mut self,
175        name: impl Into<ImmutableString>,
176        new_value: impl Into<MaterialProperty>,
177    ) {
178        self.properties.insert(name.into(), new_value.into());
179    }
180
181    /// Removes the property from the group. The renderer will use shader defaults for this property.
182    pub fn unset_property(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialProperty> {
183        self.properties.remove(&name.into())
184    }
185
186    /// Returns immutable reference to internal property storage.
187    pub fn properties(&self) -> &FxHashMap<ImmutableString, MaterialProperty> {
188        &self.properties
189    }
190}
191
192/// A set of possible material property types.
193#[derive(Debug, Visit, Clone, Reflect, AsRefStr, EnumString, VariantNames, TypeUuidProvider)]
194#[type_uuid(id = "1c25018d-ab6e-4dca-99a6-e3d9639bc33c")]
195pub enum MaterialProperty {
196    /// Real number.
197    Float(f32),
198
199    /// Real number array.
200    FloatArray(Vec<f32>),
201
202    /// Integer number.
203    Int(i32),
204
205    /// Integer number array.
206    IntArray(Vec<i32>),
207
208    /// Natural number.
209    UInt(u32),
210
211    /// Natural number array.
212    UIntArray(Vec<u32>),
213
214    /// Two-dimensional vector.
215    Vector2(Vector2<f32>),
216
217    /// Two-dimensional vector array.
218    Vector2Array(Vec<Vector2<f32>>),
219
220    /// Three-dimensional vector.
221    Vector3(Vector3<f32>),
222
223    /// Three-dimensional vector array.
224    Vector3Array(Vec<Vector3<f32>>),
225
226    /// Four-dimensional vector.
227    Vector4(Vector4<f32>),
228
229    /// Four-dimensional vector array.
230    Vector4Array(Vec<Vector4<f32>>),
231
232    /// 2x2 Matrix.
233    Matrix2(Matrix2<f32>),
234
235    /// 2x2 Matrix array.
236    Matrix2Array(Vec<Matrix2<f32>>),
237
238    /// 3x3 Matrix.
239    Matrix3(Matrix3<f32>),
240
241    /// 3x3 Matrix array.
242    Matrix3Array(Vec<Matrix3<f32>>),
243
244    /// 4x4 Matrix.
245    Matrix4(Matrix4<f32>),
246
247    /// 4x4 Matrix array.
248    Matrix4Array(Vec<Matrix4<f32>>),
249
250    /// Boolean value.
251    Bool(bool),
252
253    /// An sRGB color.
254    ///
255    /// # Conversion
256    ///
257    /// The colors you see on your monitor are in sRGB color space, this is fine for simple cases
258    /// of rendering, but not for complex things like lighting. Such things require color to be
259    /// linear. Value of this variant will be automatically **converted to linear color space**
260    /// before it passed to shader. If you want to pass sRGB color ("as is"), then use
261    /// [`MaterialProperty::Vector4`].
262    Color(Color),
263}
264
265macro_rules! impl_from {
266    ($variant:ident => $value_type:ty) => {
267        impl From<$value_type> for MaterialProperty {
268            fn from(value: $value_type) -> Self {
269                Self::$variant(value)
270            }
271        }
272    };
273}
274
275impl_from!(Float => f32);
276impl_from!(FloatArray => Vec<f32>);
277impl_from!(Int => i32);
278impl_from!(IntArray => Vec<i32>);
279impl_from!(UInt => u32);
280impl_from!(UIntArray => Vec<u32>);
281impl_from!(Vector2 => Vector2<f32>);
282impl_from!(Vector2Array => Vec<Vector2<f32>>);
283impl_from!(Vector3 => Vector3<f32>);
284impl_from!(Vector3Array => Vec<Vector3<f32>>);
285impl_from!(Vector4 => Vector4<f32>);
286impl_from!(Vector4Array => Vec<Vector4<f32>>);
287impl_from!(Matrix2 => Matrix2<f32>);
288impl_from!(Matrix2Array => Vec<Matrix2<f32>>);
289impl_from!(Matrix3 => Matrix3<f32>);
290impl_from!(Matrix3Array => Vec<Matrix3<f32>>);
291impl_from!(Matrix4 => Matrix4<f32>);
292impl_from!(Matrix4Array => Vec<Matrix4<f32>>);
293impl_from!(Bool => bool);
294impl_from!(Color => Color);
295
296impl From<Option<TextureResource>> for MaterialResourceBinding {
297    fn from(value: Option<TextureResource>) -> Self {
298        Self::Texture(MaterialTextureBinding { value })
299    }
300}
301
302impl From<TextureResource> for MaterialResourceBinding {
303    fn from(value: TextureResource) -> Self {
304        Self::Texture(MaterialTextureBinding { value: Some(value) })
305    }
306}
307
308macro_rules! define_as {
309    ($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
310        $(#[$meta])*
311        pub fn $name(&self) -> Option<$ty> {
312            if let MaterialProperty::$variant(v) = self {
313                Some(*v)
314            } else {
315                None
316            }
317        }
318    };
319}
320
321macro_rules! define_as_ref {
322    ($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
323        $(#[$meta])*
324        pub fn $name(&self) -> Option<&$ty> {
325            if let MaterialProperty::$variant(v) = self {
326                Some(v)
327            } else {
328                None
329            }
330        }
331    };
332}
333
334impl MaterialProperty {
335    define_as!(
336        /// Tries to unwrap property value as float.
337        as_float = Float -> f32
338    );
339    define_as_ref!(
340        /// Tries to unwrap property value as float array.
341        as_float_array = FloatArray -> [f32]
342    );
343    define_as!(
344        /// Tries to unwrap property value as integer.
345        as_int = Int -> i32
346    );
347    define_as_ref!(
348        /// Tries to unwrap property value as integer array.
349        as_int_array = IntArray -> [i32]
350    );
351    define_as!(
352        /// Tries to unwrap property value as unsigned integer.
353        as_uint = UInt -> u32
354    );
355    define_as_ref!(
356        /// Tries to unwrap property value as unsigned integer array.
357        as_uint_array = UIntArray -> [u32]
358    );
359    define_as!(
360        /// Tries to unwrap property value as boolean.
361        as_bool = Bool -> bool
362    );
363    define_as!(
364        /// Tries to unwrap property value as color.
365        as_color = Color -> Color
366    );
367    define_as!(
368        /// Tries to unwrap property value as two-dimensional vector.
369        as_vector2 = Vector2 -> Vector2<f32>
370    );
371    define_as_ref!(
372        /// Tries to unwrap property value as two-dimensional vector array.
373        as_vector2_array = Vector2Array -> [Vector2<f32>]
374    );
375    define_as!(
376        /// Tries to unwrap property value as three-dimensional vector.
377        as_vector3 = Vector3 -> Vector3<f32>
378    );
379    define_as_ref!(
380        /// Tries to unwrap property value as three-dimensional vector array.
381        as_vector3_array = Vector3Array -> [Vector3<f32>]
382    );
383    define_as!(
384        /// Tries to unwrap property value as four-dimensional vector.
385        as_vector4 = Vector4 -> Vector4<f32>
386    );
387    define_as_ref!(
388        /// Tries to unwrap property value as four-dimensional vector array.
389        as_vector4_array = Vector4Array -> [Vector4<f32>]
390    );
391    define_as!(
392        /// Tries to unwrap property value as 2x2 matrix.
393        as_matrix2 = Matrix2 -> Matrix2<f32>
394    );
395    define_as_ref!(
396        /// Tries to unwrap property value as 2x2 matrix array.
397        as_matrix2_array = Matrix2Array -> [Matrix2<f32>]
398    );
399    define_as!(
400        /// Tries to unwrap property value as 3x3 matrix.
401        as_matrix3 = Matrix3 -> Matrix3<f32>
402    );
403    define_as_ref!(
404        /// Tries to unwrap property value as 3x3 matrix array.
405        as_matrix3_array = Matrix3Array -> [Matrix3<f32>]
406    );
407    define_as!(
408        /// Tries to unwrap property value as 4x4 matrix.
409        as_matrix4 = Matrix4 -> Matrix4<f32>
410    );
411    define_as_ref!(
412        /// Tries to unwrap property value as 4x4 matrix array.
413        as_matrix4_array = Matrix4Array -> [Matrix4<f32>]
414    );
415}
416
417impl Default for MaterialProperty {
418    fn default() -> Self {
419        Self::Float(0.0)
420    }
421}
422
423/// Material defines a set of resource bindings for a shader. It could be textures and property groups.
424/// Textures contains graphical information (such as diffuse, normal, height, emission, etc. maps),
425/// while property groups contains numerical values (matrices, vectors, numbers, etc.).
426///
427/// Each resource binding can be changed in runtime giving you the ability to create animated materials.
428/// However, in practice most materials are static, this means that once it created, it won't be
429/// changed anymore.
430///
431/// Please keep in mind that the actual "rules" of drawing an entity are stored in the shader,
432/// **material is only a storage** for specific uses of the shader.
433///
434/// Multiple materials can share the same shader, for example standard shader covers 95% of most
435/// common use cases, and it is shared across multiple materials. The only difference are property
436/// values, for example you can draw multiple cubes using the same shader, but with different
437/// textures.
438///
439/// Material itself can be shared across multiple places as well as the shader. This gives you the
440/// ability to render multiple objects with the same material efficiently.
441///
442/// # Performance
443///
444/// It is very important to re-use materials as much as possible, because the amount of materials used
445/// per frame significantly correlates with performance. The more unique materials you have per frame,
446/// the more work has to be done by the renderer and the video driver to render a frame and the more time
447/// the frame will require for rendering, thus lowering your FPS.
448///
449/// # Examples
450///
451/// A material can only be created using a shader instance, every material must have a shader. The
452/// shader provides information about its resources. Default values of each property defined in the
453/// shader.
454///
455/// ## Standard material
456///
457/// Usually standard shader is enough for most cases, [`Material`] even has a [`Material::standard()`]
458/// method to create a material with standard shader:
459///
460/// ```no_run
461/// # use fyrox_impl::{
462/// #     material::shader::{ShaderResource, SamplerFallback},
463/// #     asset::manager::ResourceManager,
464/// #     material::{Material, MaterialProperty},
465/// #     core::sstorage::ImmutableString,
466/// # };
467/// # use fyrox_impl::resource::texture::Texture;
468///
469/// fn create_brick_material(resource_manager: ResourceManager) -> Material {
470///     let mut material = Material::standard();
471///
472///     material.bind(
473///         "diffuseTexture",
474///         resource_manager.request::<Texture>("Brick_DiffuseTexture.jpg")
475///     );
476///
477///     material
478/// }
479/// ```
480///
481/// As you can see it is pretty simple with standard material, all you need is to set values to desired
482/// properties and you good to go. All you need to do is to apply the material, for example it could be
483/// mesh surface or some other place that supports materials. For the full list of properties of the
484/// standard shader see [shader module docs](self::shader).
485///
486/// ## Custom material
487///
488/// Custom materials is a bit more complex, you need to get a shader instance using the resource manager
489/// (or create a shader in-place by embedding its source code in the binary) and then create the material
490/// and populate it with a set of property values.
491///
492/// ```no_run
493/// # use fyrox_impl::{
494/// #     asset::manager::ResourceManager,
495/// #     material::{Material, MaterialProperty},
496/// #     core::{sstorage::ImmutableString, algebra::Vector3}
497/// # };
498/// # use fyrox_impl::material::shader::Shader;
499///
500/// async fn create_grass_material(resource_manager: ResourceManager) -> Material {
501///     let shader = resource_manager.request::<Shader>("my_grass_shader.ron").await.unwrap();
502///
503///     // Here we assume that the material really has the properties defined below.
504///     let mut material = Material::from_shader(shader);
505///
506///     material.set_property("windDirection", Vector3::new(1.0, 0.0, 0.5));
507///
508///     material
509/// }
510/// ```
511///
512/// As you can see it is slightly more complex that with the standard shader. The main difference here is
513/// that we using resource manager to get shader instance, and then we just use the instance to create
514/// material instance. Then we populate properties as usual.
515#[derive(Debug, Clone, Reflect)]
516pub struct Material {
517    shader: ShaderResource,
518    resource_bindings: FxHashMap<ImmutableString, MaterialResourceBinding>,
519}
520
521#[derive(Debug, Visit, Clone, Reflect)]
522enum OldMaterialProperty {
523    Float(f32),
524    FloatArray(Vec<f32>),
525    Int(i32),
526    IntArray(Vec<i32>),
527    UInt(u32),
528    UIntArray(Vec<u32>),
529    Vector2(Vector2<f32>),
530    Vector2Array(Vec<Vector2<f32>>),
531    Vector3(Vector3<f32>),
532    Vector3Array(Vec<Vector3<f32>>),
533    Vector4(Vector4<f32>),
534    Vector4Array(Vec<Vector4<f32>>),
535    Matrix2(Matrix2<f32>),
536    Matrix2Array(Vec<Matrix2<f32>>),
537    Matrix3(Matrix3<f32>),
538    Matrix3Array(Vec<Matrix3<f32>>),
539    Matrix4(Matrix4<f32>),
540    Matrix4Array(Vec<Matrix4<f32>>),
541    Bool(bool),
542    Color(Color),
543    Sampler {
544        value: Option<TextureResource>,
545        fallback: SamplerFallback,
546    },
547}
548
549impl Default for OldMaterialProperty {
550    fn default() -> Self {
551        Self::Float(0.0)
552    }
553}
554
555impl Visit for Material {
556    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
557        let mut region = visitor.enter_region(name)?;
558
559        let mut shader = if region.is_reading() {
560            // It is very important to give a proper default state to the shader resource
561            // here. Its standard default is set to shared "Standard" shader. If it is left
562            // as is, deserialization will modify the "Standard" shader and this will lead
563            // to "amazing" results and hours of debugging.
564            ShaderResource::default()
565        } else {
566            self.shader.clone()
567        };
568        shader.visit("Shader", &mut region)?;
569        self.shader = shader;
570
571        if region.is_reading() {
572            // Backward compatibility.
573            let mut old_properties = FxHashMap::<ImmutableString, OldMaterialProperty>::default();
574            if old_properties.visit("Properties", &mut region).is_ok() {
575                for (name, old_property) in &old_properties {
576                    if let OldMaterialProperty::Sampler { value, .. } = old_property {
577                        self.bind(
578                            name.clone(),
579                            MaterialResourceBinding::Texture(MaterialTextureBinding {
580                                value: value.clone(),
581                            }),
582                        )
583                    }
584                }
585
586                let properties = self.try_get_or_insert_property_group("properties");
587
588                for (name, old_property) in old_properties {
589                    match old_property {
590                        OldMaterialProperty::Float(v) => properties.set_property(name, v),
591                        OldMaterialProperty::FloatArray(v) => properties.set_property(name, v),
592                        OldMaterialProperty::Int(v) => properties.set_property(name, v),
593                        OldMaterialProperty::IntArray(v) => properties.set_property(name, v),
594                        OldMaterialProperty::UInt(v) => properties.set_property(name, v),
595                        OldMaterialProperty::UIntArray(v) => properties.set_property(name, v),
596                        OldMaterialProperty::Vector2(v) => properties.set_property(name, v),
597                        OldMaterialProperty::Vector2Array(v) => properties.set_property(name, v),
598                        OldMaterialProperty::Vector3(v) => properties.set_property(name, v),
599                        OldMaterialProperty::Vector3Array(v) => properties.set_property(name, v),
600                        OldMaterialProperty::Vector4(v) => properties.set_property(name, v),
601                        OldMaterialProperty::Vector4Array(v) => properties.set_property(name, v),
602                        OldMaterialProperty::Matrix2(v) => properties.set_property(name, v),
603                        OldMaterialProperty::Matrix2Array(v) => properties.set_property(name, v),
604                        OldMaterialProperty::Matrix3(v) => properties.set_property(name, v),
605                        OldMaterialProperty::Matrix3Array(v) => properties.set_property(name, v),
606                        OldMaterialProperty::Matrix4(v) => properties.set_property(name, v),
607                        OldMaterialProperty::Matrix4Array(v) => properties.set_property(name, v),
608                        OldMaterialProperty::Bool(v) => properties.set_property(name, v),
609                        OldMaterialProperty::Color(v) => properties.set_property(name, v),
610                        _ => (),
611                    };
612                }
613            } else {
614                self.resource_bindings
615                    .visit("ResourceBindings", &mut region)?;
616            }
617        } else {
618            self.resource_bindings
619                .visit("ResourceBindings", &mut region)?;
620        }
621
622        Ok(())
623    }
624}
625
626impl Default for Material {
627    fn default() -> Self {
628        Material::standard()
629    }
630}
631
632impl TypeUuidProvider for Material {
633    fn type_uuid() -> Uuid {
634        uuid!("0e54fe44-0c58-4108-a681-d6eefc88c234")
635    }
636}
637
638impl ResourceData for Material {
639    fn type_uuid(&self) -> Uuid {
640        <Self as TypeUuidProvider>::type_uuid()
641    }
642
643    fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
644        let mut visitor = Visitor::new();
645        self.visit("Material", &mut visitor)?;
646        visitor.save_binary(path)?;
647        Ok(())
648    }
649
650    fn can_be_saved(&self) -> bool {
651        true
652    }
653}
654
655/// A set of possible errors that can occur when working with materials.
656#[derive(Debug)]
657pub enum MaterialError {
658    /// Unable to read data source.
659    Visit(VisitError),
660}
661
662impl From<VisitError> for MaterialError {
663    fn from(value: VisitError) -> Self {
664        Self::Visit(value)
665    }
666}
667
668impl From<FileLoadError> for MaterialError {
669    fn from(value: FileLoadError) -> Self {
670        Self::Visit(VisitError::FileLoadError(value))
671    }
672}
673
674impl Display for MaterialError {
675    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
676        match self {
677            MaterialError::Visit(e) => {
678                write!(f, "Failed to visit data source. Reason: {e:?}")
679            }
680        }
681    }
682}
683
684lazy_static! {
685    /// Standard PBR material. Keep in mind that this material is global, any modification
686    /// of it will reflect on every other usage of it.
687    pub static ref STANDARD: BuiltInResource<Material> = BuiltInResource::new_no_source(
688        MaterialResource::new_ok(
689            "__StandardMaterial".into(),
690            Material::from_shader(ShaderResource::standard()),
691        )
692    );
693
694    /// Standard 2D material. Keep in mind that this material is global, any modification
695    /// of it will reflect on every other usage of it.
696    pub static ref STANDARD_2D: BuiltInResource<Material> = BuiltInResource::new_no_source(
697        MaterialResource::new_ok(
698            "__Standard2DMaterial".into(),
699            Material::from_shader(ShaderResource::standard_2d()),
700        )
701    );
702
703    /// Standard particle system material. Keep in mind that this material is global, any modification
704    /// of it will reflect on every other usage of it.
705    pub static ref STANDARD_PARTICLE_SYSTEM: BuiltInResource<Material> = BuiltInResource::new_no_source(
706        MaterialResource::new_ok(
707            "__StandardParticleSystemMaterial".into(),
708            Material::from_shader(ShaderResource::standard_particle_system(),),
709        )
710    );
711
712    /// Standard sprite material. Keep in mind that this material is global, any modification
713    /// of it will reflect on every other usage of it.
714    pub static ref STANDARD_SPRITE: BuiltInResource<Material> = BuiltInResource::new_no_source(
715        MaterialResource::new_ok(
716            "__StandardSpriteMaterial".into(),
717            Material::from_shader(ShaderResource::standard_sprite()),
718        )
719    );
720
721    /// Standard terrain material. Keep in mind that this material is global, any modification
722    /// of it will reflect on every other usage of it.
723    pub static ref STANDARD_TERRAIN: BuiltInResource<Material> = BuiltInResource::new_no_source(
724        MaterialResource::new_ok(
725            "__StandardTerrainMaterial".into(),
726           Material::from_shader(ShaderResource::standard_terrain()),
727        )
728    );
729
730    /// Standard two-sided material. Keep in mind that this material is global, any modification
731    /// of it will reflect on every other usage of it.
732    pub static ref STANDARD_TWOSIDES: BuiltInResource<Material> = BuiltInResource::new_no_source(
733        MaterialResource::new_ok(
734            "__StandardTwoSidesMaterial".into(),
735          Material::from_shader(ShaderResource::standard_twosides()),
736        )
737    );
738}
739
740impl Material {
741    /// Creates a new instance of material with the standard shader.
742    ///
743    /// # Example
744    ///
745    /// ```no_run
746    /// # use fyrox_impl::{
747    /// #     material::shader::{ShaderResource, SamplerFallback},
748    /// #     asset::manager::ResourceManager,
749    /// #     material::{Material, MaterialProperty},
750    /// #     core::sstorage::ImmutableString
751    /// # };
752    /// # use fyrox_impl::resource::texture::Texture;
753    ///
754    /// fn create_brick_material(resource_manager: ResourceManager) -> Material {
755    ///     let mut material = Material::standard();
756    ///
757    ///     material.bind(
758    ///         "diffuseTexture",
759    ///         resource_manager.request::<Texture>("Brick_DiffuseTexture.jpg")
760    ///     );
761    ///
762    ///     material
763    /// }
764    /// ```
765    pub fn standard() -> Self {
766        Self::from_shader(ShaderResource::standard())
767    }
768
769    /// Creates new instance of standard 2D material.
770    pub fn standard_2d() -> Self {
771        Self::from_shader(ShaderResource::standard_2d())
772    }
773
774    /// Creates new instance of standard 2D material.
775    pub fn standard_particle_system() -> Self {
776        Self::from_shader(ShaderResource::standard_particle_system())
777    }
778
779    /// Creates new instance of standard sprite material.
780    pub fn standard_sprite() -> Self {
781        Self::from_shader(ShaderResource::standard_sprite())
782    }
783
784    /// Creates new instance of standard material that renders both sides of a face.
785    pub fn standard_two_sides() -> Self {
786        Self::from_shader(ShaderResource::standard_twosides())
787    }
788
789    /// Creates new instance of standard terrain material.
790    pub fn standard_terrain() -> Self {
791        Self::from_shader(ShaderResource::standard_terrain())
792    }
793
794    /// Creates new instance of standard tile material.
795    pub fn standard_tile() -> Self {
796        Self::from_shader(ShaderResource::standard_tile())
797    }
798
799    /// Creates a new material instance with given shader. By default, a material does not store any
800    /// resource bindings. In this case the renderer will use shader default values for rendering.
801    /// Materials could be considered as container with values that overwrites shader values.
802    ///
803    /// # Example
804    ///
805    /// ```no_run
806    /// # use fyrox_impl::{
807    /// #     asset::manager::ResourceManager,
808    /// #     material::{Material, MaterialProperty},
809    /// #     core::{sstorage::ImmutableString, algebra::Vector3}
810    /// # };
811    /// # use fyrox_impl::material::shader::Shader;
812    ///
813    /// async fn create_grass_material(resource_manager: ResourceManager) -> Material {
814    ///     let shader = resource_manager.request::<Shader>("my_grass_shader.ron").await.unwrap();
815    ///
816    ///     // Here we assume that the material really has the properties defined below.
817    ///     let mut material = Material::from_shader(shader);
818    ///
819    ///     material.set_property("windDirection", Vector3::new(1.0, 0.0, 0.5));
820    ///
821    ///     material
822    /// }
823    /// ```
824    pub fn from_shader(shader: ShaderResource) -> Self {
825        Self {
826            shader,
827            resource_bindings: Default::default(),
828        }
829    }
830
831    /// Loads a material from file.
832    pub async fn from_file<P>(
833        path: P,
834        io: &dyn ResourceIo,
835        resource_manager: ResourceManager,
836    ) -> Result<Self, MaterialError>
837    where
838        P: AsRef<Path>,
839    {
840        let content = io.load_file(path.as_ref()).await?;
841        let mut material = Material {
842            shader: Default::default(),
843            resource_bindings: Default::default(),
844        };
845        let mut visitor = Visitor::load_from_memory(&content)?;
846        visitor.blackboard.register(Arc::new(resource_manager));
847        material.visit("Material", &mut visitor)?;
848        Ok(material)
849    }
850
851    /// Searches for a resource binding with the given name and returns immutable reference to it
852    /// (if any).
853    ///
854    /// # Complexity
855    ///
856    /// O(1)
857    pub fn binding_ref(
858        &self,
859        name: impl Into<ImmutableString>,
860    ) -> Option<&MaterialResourceBinding> {
861        let name = name.into();
862        self.resource_bindings.get(&name)
863    }
864
865    /// Searches for a resource binding with the given name and returns mutable reference to it,
866    /// allowing you to modify the value.
867    ///
868    /// # Complexity
869    ///
870    /// O(1)
871    pub fn binding_mut(
872        &mut self,
873        name: impl Into<ImmutableString>,
874    ) -> Option<&mut MaterialResourceBinding> {
875        let name = name.into();
876        self.resource_bindings.get_mut(&name)
877    }
878
879    /// Searches for a texture with the given name.
880    pub fn texture_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialTextureBinding> {
881        if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_ref(name) {
882            Some(binding)
883        } else {
884            None
885        }
886    }
887
888    /// Searches for a texture with the given name.
889    pub fn texture_mut(
890        &mut self,
891        name: impl Into<ImmutableString>,
892    ) -> Option<&mut MaterialTextureBinding> {
893        if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_mut(name) {
894            Some(binding)
895        } else {
896            None
897        }
898    }
899
900    /// Searches for a property group binding with the given name and returns immutable reference to it
901    /// (if any).
902    ///
903    /// # Complexity
904    ///
905    /// O(1)
906    ///
907    /// # Examples
908    ///
909    /// ```no_run
910    /// # use fyrox_impl::core::sstorage::ImmutableString;
911    /// # use fyrox_impl::material::Material;
912    ///
913    /// let mut material = Material::standard();
914    ///
915    /// let color = material
916    ///     .property_group_ref("properties")
917    ///     .unwrap()
918    ///     .property_ref("diffuseColor")
919    ///     .unwrap()
920    ///     .as_color();
921    /// ```
922    pub fn property_group_ref(
923        &self,
924        name: impl Into<ImmutableString>,
925    ) -> Option<&MaterialPropertyGroup> {
926        self.binding_ref(name).and_then(|binding| match binding {
927            MaterialResourceBinding::Texture { .. } => None,
928            MaterialResourceBinding::PropertyGroup(group) => Some(group),
929        })
930    }
931
932    /// Searches for a property group binding with the given name and returns immutable reference to it
933    /// (if any).
934    ///
935    /// # Complexity
936    ///
937    /// O(1)
938    ///
939    /// # Examples
940    ///
941    /// ```no_run
942    /// # use fyrox_core::color::Color;
943    /// use fyrox_impl::core::sstorage::ImmutableString;
944    /// # use fyrox_impl::material::Material;
945    ///
946    /// let mut material = Material::standard();
947    ///
948    /// let color = material
949    ///     .property_group_mut("properties")
950    ///     .unwrap()
951    ///     .set_property("diffuseColor", Color::RED);
952    /// ```
953    pub fn property_group_mut(
954        &mut self,
955        name: impl Into<ImmutableString>,
956    ) -> Option<&mut MaterialPropertyGroup> {
957        self.binding_mut(name).and_then(|binding| match binding {
958            MaterialResourceBinding::Texture { .. } => None,
959            MaterialResourceBinding::PropertyGroup(group) => Some(group),
960        })
961    }
962
963    /// Tries to find a property group with the given name, creates a new group with the given name
964    /// if there's no such group.
965    pub fn try_get_or_insert_property_group(
966        &mut self,
967        name: impl Into<ImmutableString>,
968    ) -> &mut MaterialPropertyGroup {
969        let name = name.into();
970        if let MaterialResourceBinding::PropertyGroup(group) = self
971            .resource_bindings
972            .entry(name.clone())
973            .or_insert_with(|| {
974                MaterialResourceBinding::PropertyGroup(MaterialPropertyGroup::default())
975            })
976        {
977            group
978        } else {
979            panic!("There's already a material resource binding with {name}!");
980        }
981    }
982
983    /// Sets new value of the resource binding with given name.
984    ///
985    /// # Type checking
986    ///
987    /// A new value must have the same type as in shader, otherwise an error will be generated at
988    /// attempt to render something with this material.
989    ///
990    /// # Example
991    ///
992    /// ```no_run
993    /// # use fyrox_impl::material::{Material, MaterialProperty};
994    /// # use fyrox_impl::core::color::Color;
995    /// # use fyrox_impl::core::sstorage::ImmutableString;
996    ///
997    /// let mut material = Material::standard();
998    ///
999    /// material.set_property("diffuseColor", Color::WHITE);
1000    /// ```
1001    pub fn bind(
1002        &mut self,
1003        name: impl Into<ImmutableString>,
1004        new_value: impl Into<MaterialResourceBinding>,
1005    ) {
1006        self.resource_bindings.insert(name.into(), new_value.into());
1007    }
1008
1009    /// Tries to remove a resource bound to the given name.
1010    pub fn unbind(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialResourceBinding> {
1011        self.resource_bindings.remove(&name.into())
1012    }
1013
1014    /// Sets new value of the property with given name to the property group with `properties` name.
1015    /// It is a standard property group, that could be used to store pretty much any values.
1016    ///
1017    /// # Type checking
1018    ///
1019    /// A new value must have the same type as in shader, otherwise an error will be generated at
1020    /// attempt to render something with this material.
1021    ///
1022    /// # Example
1023    ///
1024    /// ```no_run
1025    /// # use fyrox_impl::material::{Material, MaterialProperty};
1026    /// # use fyrox_impl::core::color::Color;
1027    /// # use fyrox_impl::core::sstorage::ImmutableString;
1028    ///
1029    /// let mut material = Material::standard();
1030    ///
1031    /// material.set_property("diffuseColor", Color::WHITE);
1032    ///
1033    /// // A full equivalent of the above:
1034    /// material.try_get_or_insert_property_group("properties")
1035    ///         .set_property("diffuseColor", Color::WHITE);
1036    /// ```
1037    pub fn set_property(
1038        &mut self,
1039        name: impl Into<ImmutableString>,
1040        new_value: impl Into<MaterialProperty>,
1041    ) {
1042        self.try_get_or_insert_property_group("properties")
1043            .set_property(name, new_value);
1044    }
1045
1046    /// Returns a reference to current shader.
1047    pub fn shader(&self) -> &ShaderResource {
1048        &self.shader
1049    }
1050
1051    /// Returns immutable reference to internal property storage.
1052    pub fn bindings(&self) -> &FxHashMap<ImmutableString, MaterialResourceBinding> {
1053        &self.resource_bindings
1054    }
1055
1056    /// Tries to find a sampler with the given name and returns its texture (if any).
1057    pub fn texture(&self, name: impl Into<ImmutableString>) -> Option<TextureResource> {
1058        self.resource_bindings.get(&name.into()).and_then(|v| {
1059            if let MaterialResourceBinding::Texture(ref binding) = v {
1060                binding.value.clone()
1061            } else {
1062                None
1063            }
1064        })
1065    }
1066}
1067
1068/// Material resource is a material instance that can be used across multiple objects. It is useful
1069/// when you need to have multiple objects that have the same material.
1070///
1071/// Material resource is also tells a renderer that this material can be used for efficient rendering -
1072/// the renderer will be able to optimize rendering when it knows that multiple objects share the
1073/// same material.
1074pub type MaterialResource = Resource<Material>;
1075
1076/// Extension methods for material resource.
1077pub trait MaterialResourceExtension {
1078    /// Creates a new material resource.
1079    ///
1080    /// # Hot Reloading
1081    ///
1082    /// You must use this method to create materials, if you want hot reloading to be reliable and
1083    /// prevent random crashes. Unlike [`Resource::new_ok`], this method ensures that correct vtable
1084    /// is used.
1085    fn new(material: Material) -> Self;
1086
1087    /// Creates a deep copy of the material resource.
1088    fn deep_copy(&self) -> MaterialResource;
1089
1090    /// Creates a deep copy of the material resource and marks it as procedural.
1091    fn deep_copy_as_embedded(&self) -> MaterialResource {
1092        let material = self.deep_copy();
1093        let mut header = material.header();
1094        header.kind.make_embedded();
1095        drop(header);
1096        material
1097    }
1098}
1099
1100impl MaterialResourceExtension for MaterialResource {
1101    #[inline(never)] // Prevents vtable mismatch when doing hot reloading.
1102    fn new(material: Material) -> Self {
1103        Self::new_ok(ResourceKind::Embedded, material)
1104    }
1105
1106    fn deep_copy(&self) -> MaterialResource {
1107        let material_state = self.header();
1108        let kind = material_state.kind.clone();
1109        match material_state.state {
1110            ResourceState::Pending { .. } => MaterialResource::new_pending(kind),
1111            ResourceState::LoadError { ref error } => {
1112                MaterialResource::new_load_error(kind.clone(), error.clone())
1113            }
1114            ResourceState::Ok(ref material) => MaterialResource::new_ok(
1115                kind,
1116                Downcast::as_any(&**material)
1117                    .downcast_ref::<Material>()
1118                    .unwrap()
1119                    .clone(),
1120            ),
1121        }
1122    }
1123}
1124
1125pub(crate) fn visit_old_material(region: &mut RegionGuard) -> Option<MaterialResource> {
1126    let mut old_material = Arc::new(Mutex::new(Material::default()));
1127    if let Ok(mut inner) = region.enter_region("Material") {
1128        if old_material.visit("Value", &mut inner).is_ok() {
1129            return Some(MaterialResource::new_ok(
1130                Default::default(),
1131                old_material.lock().clone(),
1132            ));
1133        }
1134    }
1135    None
1136}
1137
1138pub(crate) fn visit_old_texture_as_material<F>(
1139    region: &mut RegionGuard,
1140    make_default_material: F,
1141) -> Option<MaterialResource>
1142where
1143    F: FnOnce() -> Material,
1144{
1145    let mut old_texture: Option<TextureResource> = None;
1146    if let Ok(mut inner) = region.enter_region("Texture") {
1147        if old_texture.visit("Value", &mut inner).is_ok() {
1148            let mut material = make_default_material();
1149            material.bind("diffuseTexture", old_texture);
1150            return Some(MaterialResource::new_ok(Default::default(), material));
1151        }
1152    }
1153    None
1154}