Skip to main content

librashader_pack/
lib.rs

1//! Shader preset resource handling for librashader.
2//!
3//! This crate contains facilities to load shader preset resources from a [`ShaderPreset`].
4//!
5//! Also defines abstractly the `.slangpkg` shader preset format implemented via serde derives on [`ShaderPresetPack`].
6//!
7use 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/// A buffer holding RGBA image bytes.
17#[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/// A resource for a shader preset, fully loaded into memory.
51#[derive(Debug, Clone)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53pub struct LoadedResource<M: LoadableResource> {
54    /// The fully qualified path to the resource.
55    pub data: M::ResourceType,
56    /// Meta information about the resource.
57    pub meta: M,
58}
59
60/// Trait for a resource that is loadable from disk.
61pub trait LoadableResource {
62    /// The type of the resource.
63    type ResourceType;
64    /// The error type when loading the resource.
65    type Error;
66    /// The type of options to pass to the loader.
67    type Options;
68    /// Load the resource from the path.
69    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
92/// The loaded resource information for the source code of a shader pass.
93pub type PassResource = LoadedResource<PassMeta>;
94
95/// The loaded texture resource for a shader preset.
96pub type TextureResource = LoadedResource<TextureMeta>;
97
98/// The language that the shader sources are in.
99///
100/// In the vast majority of cases, this will always be GLSL.
101#[derive(Debug, Clone)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103pub enum ShaderSourceLanguage {
104    /// GLSL
105    Glsl,
106    /// WGSL (only produced when creating a .wgsl.slangpkg
107    Wgsl,
108}
109
110/// A fully loaded-in-memory shader preset, with all paths resolved to data.
111#[derive(Debug, Clone)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub struct ShaderPresetPack {
114    /// The language that the pass shader sources are in.
115    ///
116    /// Almost always GLSL, unless created from a .wgsl.slangpkg
117    pub language: ShaderSourceLanguage,
118
119    /// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
120    /// Otherwise, the FBO after pass #N is passed a texture to next frame
121    #[cfg(feature = "parse_legacy_glsl")]
122    pub feedback_pass: i32,
123
124    /// The number of shaders enabled in the filter chain.
125    pub pass_count: i32,
126    // Everything is in Vecs because the expect number of values is well below 64.
127    /// Preset information for each shader.
128    pub passes: Vec<PassResource>,
129
130    /// Preset information for each texture.
131    pub textures: Vec<TextureResource>,
132
133    /// Preset information for each user parameter.
134    pub parameters: Vec<ParameterMeta>,
135}
136
137#[cfg(feature = "load")]
138impl ShaderPresetPack {
139    /// Load a `ShaderPack` from a [`ShaderPreset`].
140    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                        // The default PassMeta::load function is always GLSL.
162                        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    /// Pack-level implementation: the shader source is already loaded so this
183    /// is infallible — but we still return `Result` to share the trait
184    /// signature with [`ShaderPreset`].
185    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}