oxide_renderer/
descriptor.rs1use std::path::Path;
4
5use serde::{Deserialize, Serialize};
6
7use crate::shader::{BuiltinShader, ShaderSource};
8
9#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum MaterialType {
12 Lit,
13 Unlit,
14 Basic,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(tag = "source", rename_all = "snake_case")]
19pub enum ShaderDescriptor {
20 Builtin { shader: String },
21 File { path: String },
22 Inline { wgsl: String },
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct MaterialDescriptor {
27 pub name: String,
28 pub material_type: MaterialType,
29 pub shader: ShaderDescriptor,
30 pub fallback_shader: Option<String>,
31 #[serde(default)]
33 pub albedo_texture: Option<String>,
34 #[serde(default)]
36 pub normal_texture: Option<String>,
37 #[serde(default)]
39 pub roughness_texture: Option<String>,
40}
41
42#[derive(thiserror::Error, Debug)]
43pub enum MaterialDescriptorError {
44 #[error("Failed to read descriptor '{path}': {source}")]
45 Io {
46 path: String,
47 source: std::io::Error,
48 },
49 #[error("Failed to parse JSON descriptor '{path}': {source}")]
50 ParseJson {
51 path: String,
52 source: serde_json::Error,
53 },
54 #[error("Failed to parse RON descriptor '{path}': {source}")]
55 ParseRon {
56 path: String,
57 source: Box<ron::error::SpannedError>,
58 },
59 #[error("Failed to parse TOML descriptor '{path}': {source}")]
60 ParseToml {
61 path: String,
62 source: toml::de::Error,
63 },
64 #[error("Unsupported descriptor format '{ext}' for file '{path}'")]
65 UnsupportedFormat { path: String, ext: String },
66 #[error("Unknown builtin shader '{name}'")]
67 UnknownBuiltinShader { name: String },
68}
69
70impl MaterialDescriptor {
71 pub fn shader_source(&self) -> Result<ShaderSource, MaterialDescriptorError> {
72 match &self.shader {
73 ShaderDescriptor::Builtin { shader } => {
74 Ok(ShaderSource::Builtin(parse_builtin_shader(shader)?))
75 }
76 ShaderDescriptor::File { path } => Ok(ShaderSource::File(path.into())),
77 ShaderDescriptor::Inline { wgsl } => Ok(ShaderSource::WgslOwned(wgsl.clone())),
78 }
79 }
80
81 pub fn fallback_shader(&self) -> Result<BuiltinShader, MaterialDescriptorError> {
82 match &self.fallback_shader {
83 Some(name) => parse_builtin_shader(name),
84 None => Ok(BuiltinShader::Fallback),
85 }
86 }
87}
88
89pub fn load_material_descriptor(
90 path: impl AsRef<Path>,
91) -> Result<MaterialDescriptor, MaterialDescriptorError> {
92 let path = path.as_ref();
93 let raw = std::fs::read_to_string(path).map_err(|source| MaterialDescriptorError::Io {
94 path: path.display().to_string(),
95 source,
96 })?;
97
98 let ext = path
99 .extension()
100 .and_then(|e| e.to_str())
101 .unwrap_or("")
102 .to_lowercase();
103 match ext.as_str() {
104 "json" => serde_json::from_str(&raw).map_err(|source| MaterialDescriptorError::ParseJson {
105 path: path.display().to_string(),
106 source,
107 }),
108 "ron" => ron::from_str(&raw).map_err(|source| MaterialDescriptorError::ParseRon {
109 path: path.display().to_string(),
110 source: Box::new(source),
111 }),
112 "toml" => toml::from_str(&raw).map_err(|source| MaterialDescriptorError::ParseToml {
113 path: path.display().to_string(),
114 source,
115 }),
116 _ => Err(MaterialDescriptorError::UnsupportedFormat {
117 path: path.display().to_string(),
118 ext,
119 }),
120 }
121}
122
123fn parse_builtin_shader(name: &str) -> Result<BuiltinShader, MaterialDescriptorError> {
124 match name.trim().to_ascii_lowercase().as_str() {
125 "basic" => Ok(BuiltinShader::Basic),
126 "lit" => Ok(BuiltinShader::Lit),
127 "unlit" => Ok(BuiltinShader::Unlit),
128 "sky_gradient" | "skygradient" | "sky" => Ok(BuiltinShader::SkyGradient),
129 "sprite_ui" | "spriteui" | "ui" => Ok(BuiltinShader::SpriteUi),
130 "fallback" => Ok(BuiltinShader::Fallback),
131 _ => Err(MaterialDescriptorError::UnknownBuiltinShader {
132 name: name.to_string(),
133 }),
134 }
135}