1use image::{ImageError, RgbaImage};
8use librashader_preprocess::{PreprocessError, ShaderSource};
9use librashader_presets::{
10 ParameterMeta, PassMeta, PresetColorSpace, ShaderFeatures, ShaderPreset, TextureMeta,
11};
12use std::path::Path;
13
14use librashader_common::ColorSpace;
15
16#[derive(Debug, Clone)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct TextureBuffer {
20 #[cfg_attr(feature = "serde", serde(with = "serde_base64_or_bytes"))]
21 image: Vec<u8>,
22 width: u32,
23 height: u32,
24}
25
26impl From<TextureBuffer> for Option<RgbaImage> {
27 fn from(value: TextureBuffer) -> Self {
28 RgbaImage::from_raw(value.width, value.height, value.image)
29 }
30}
31
32impl AsRef<[u8]> for TextureBuffer {
33 fn as_ref(&self) -> &[u8] {
34 self.image.as_ref()
35 }
36}
37
38impl From<RgbaImage> for TextureBuffer {
39 fn from(value: RgbaImage) -> Self {
40 let width = value.width();
41 let height = value.height();
42 TextureBuffer {
43 image: value.into_raw(),
44 width,
45 height,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53pub struct LoadedResource<M: LoadableResource> {
54 pub data: M::ResourceType,
56 pub meta: M,
58}
59
60pub trait LoadableResource {
62 type ResourceType;
64 type Error;
66 type Options;
68 fn load(path: &Path, options: Self::Options) -> Result<Self::ResourceType, Self::Error>;
70}
71
72impl LoadableResource for PassMeta {
73 type ResourceType = ShaderSource;
74 type Error = PreprocessError;
75 type Options = ShaderFeatures;
76
77 fn load(path: &Path, options: Self::Options) -> Result<Self::ResourceType, Self::Error> {
78 ShaderSource::load(path, options)
79 }
80}
81
82impl LoadableResource for TextureMeta {
83 type ResourceType = TextureBuffer;
84 type Error = ImageError;
85 type Options = ();
86
87 fn load(path: &Path, _options: Self::Options) -> Result<Self::ResourceType, Self::Error> {
88 image::open(path).map(|img| TextureBuffer::from(img.to_rgba8()))
89 }
90}
91
92pub type PassResource = LoadedResource<PassMeta>;
94
95pub type TextureResource = LoadedResource<TextureMeta>;
97
98#[derive(Debug, Clone)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103pub enum ShaderSourceLanguage {
104 Glsl,
106 Wgsl,
108}
109
110#[derive(Debug, Clone)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub struct ShaderPresetPack {
114 pub language: ShaderSourceLanguage,
118
119 #[cfg(feature = "parse_legacy_glsl")]
122 pub feedback_pass: i32,
123
124 pub pass_count: i32,
126 pub passes: Vec<PassResource>,
129
130 pub textures: Vec<TextureResource>,
132
133 pub parameters: Vec<ParameterMeta>,
135}
136
137#[cfg(feature = "load")]
138impl ShaderPresetPack {
139 pub fn load_from_preset<E>(preset: ShaderPreset) -> Result<ShaderPresetPack, E>
141 where
142 E: From<PreprocessError>,
143 E: From<ImageError>,
144 E: Send,
145 {
146 use rayon::prelude::*;
147
148 let shaders_iter = preset.passes.into_par_iter();
149 let textures_iter = preset.textures.into_par_iter();
150
151 Ok(ShaderPresetPack {
152 language: ShaderSourceLanguage::Glsl,
153
154 #[cfg(feature = "parse_legacy_glsl")]
155 feedback_pass: preset.feedback_pass,
156
157 pass_count: preset.pass_count,
158 passes: shaders_iter
159 .map(|v| {
160 Ok::<_, E>(PassResource {
161 data: PassMeta::load(v.path.as_path(), preset.features)?,
163 meta: v.meta,
164 })
165 })
166 .collect::<Result<Vec<_>, _>>()?,
167 textures: textures_iter
168 .into_par_iter()
169 .map(|t| {
170 Ok::<_, E>(TextureResource {
171 data: TextureMeta::load(t.path.as_path(), ())?,
172 meta: t.meta,
173 })
174 })
175 .collect::<Result<Vec<_>, _>>()?,
176 parameters: preset.parameters,
177 })
178 }
179}
180
181impl PresetColorSpace for ShaderPresetPack {
182 fn color_space(&self) -> Result<ColorSpace, PreprocessError> {
186 let Some(last) = self.passes.last() else {
187 return Ok(ColorSpace::Sdr);
188 };
189 let effective_format = last.meta.get_format_override().unwrap_or(last.data.format);
190
191 Ok(match effective_format {
192 librashader_common::ImageFormat::A2B10G10R10UnormPack32 => ColorSpace::Hdr10,
193 librashader_common::ImageFormat::R16G16B16A16Sfloat => ColorSpace::ScRgb,
194 _ => ColorSpace::Sdr,
195 })
196 }
197}
198
199#[cfg(feature = "serde")]
200mod serde_base64_or_bytes {
201 use base64::display::Base64Display;
202 use base64::engine::general_purpose::STANDARD;
203 use base64::Engine;
204 use serde::{Deserializer, Serializer};
205
206 #[allow(clippy::ptr_arg)]
207 pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
208 if s.is_human_readable() {
209 s.collect_str(&Base64Display::new(v, &STANDARD))
210 } else {
211 serde_bytes::serialize(v, s)
212 }
213 }
214
215 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
216 if d.is_human_readable() {
217 struct Base64Visitor;
218 impl<'de> serde::de::Visitor<'de> for Base64Visitor {
219 type Value = Vec<u8>;
220
221 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
222 formatter.write_str("a base64 encoded string")
223 }
224
225 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
226 where
227 E: serde::de::Error,
228 {
229 self.visit_bytes(v.as_ref())
230 }
231
232 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
233 where
234 E: serde::de::Error,
235 {
236 self.visit_bytes(v.as_ref())
237 }
238
239 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
240 where
241 E: serde::de::Error,
242 {
243 STANDARD.decode(v).map_err(serde::de::Error::custom)
244 }
245
246 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
247 where
248 E: serde::de::Error,
249 {
250 self.visit_bytes(v.as_ref())
251 }
252 }
253
254 d.deserialize_str(Base64Visitor)
255 } else {
256 serde_bytes::deserialize(d)
257 }
258 }
259}
260
261#[cfg(test)]
262mod test {
263 use crate::ShaderPresetPack;
264 use librashader_presets::{ShaderFeatures, ShaderPreset};
265 use std::fs::File;
266 use std::io::Write;
267
268 #[test]
269 fn test() {
270 let preset = ShaderPreset::try_parse(
271 "../test/shaders_slang/crt/crt-royale.slangp",
272 ShaderFeatures::NONE,
273 )
274 .unwrap();
275 let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
276 let mut file = File::create("crt-royale.slangpack.json").unwrap();
277 file.write_all(serde_json::to_vec_pretty(&resolved).unwrap().as_ref())
278 .unwrap();
279 }
280
281 #[test]
282 fn test_rmp() {
283 let preset = ShaderPreset::try_parse(
284 "../test/shaders_slang/crt/crt-royale.slangp",
285 ShaderFeatures::NONE,
286 )
287 .unwrap();
288 let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
289 let mut file = File::create("crt-royale.slangpack").unwrap();
290 file.write_all(rmp_serde::to_vec(&resolved).unwrap().as_ref())
291 .unwrap();
292 }
293}