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 `.slangpack` shader format implemented via serde derives on [`ShaderPresetPack`].
6//!
7use image::{ImageError, RgbaImage};
8use librashader_preprocess::{PreprocessError, ShaderSource};
9use librashader_presets::{ParameterMeta, PassMeta, ShaderFeatures, ShaderPreset, TextureMeta};
10use std::path::Path;
11
12#[cfg(not(target_arch = "wasm32"))]
13use rayon::prelude::*;
14
15/// A buffer holding RGBA image bytes.
16#[derive(Debug, Clone)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct TextureBuffer {
19    #[cfg_attr(feature = "serde", serde(with = "serde_base64_or_bytes"))]
20    image: Vec<u8>,
21    width: u32,
22    height: u32,
23}
24
25impl From<TextureBuffer> for Option<RgbaImage> {
26    fn from(value: TextureBuffer) -> Self {
27        RgbaImage::from_raw(value.width, value.height, value.image)
28    }
29}
30
31impl AsRef<[u8]> for TextureBuffer {
32    fn as_ref(&self) -> &[u8] {
33        self.image.as_ref()
34    }
35}
36
37impl From<RgbaImage> for TextureBuffer {
38    fn from(value: RgbaImage) -> Self {
39        let width = value.width();
40        let height = value.height();
41        TextureBuffer {
42            image: value.into_raw(),
43            width,
44            height,
45        }
46    }
47}
48
49/// A resource for a shader preset, fully loaded into memory.
50#[derive(Debug, Clone)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct LoadedResource<M: LoadableResource> {
53    /// The fully qualified path to the texture.
54    pub data: M::ResourceType,
55    /// Meta information about the texture.
56    pub meta: M,
57}
58
59/// Trait for a resource that is loadable from disk.
60pub trait LoadableResource {
61    /// The type of the resource.
62    type ResourceType;
63    /// The error type when loading the resource.
64    type Error;
65    /// The type of options to pass to the loader.
66    type Options;
67    /// Load the resource from the path.
68    fn load(path: &Path, options: Self::Options) -> Result<Self::ResourceType, Self::Error>;
69}
70
71impl LoadableResource for PassMeta {
72    type ResourceType = ShaderSource;
73    type Error = PreprocessError;
74    type Options = ShaderFeatures;
75
76    fn load(path: &Path, options: Self::Options) -> Result<Self::ResourceType, Self::Error> {
77        ShaderSource::load(path, options)
78    }
79}
80
81impl LoadableResource for TextureMeta {
82    type ResourceType = TextureBuffer;
83    type Error = ImageError;
84    type Options = ();
85
86    fn load(path: &Path, _options: Self::Options) -> Result<Self::ResourceType, Self::Error> {
87        image::open(path).map(|img| TextureBuffer::from(img.to_rgba8()))
88    }
89}
90
91/// The loaded resource information for the source code of a shader pass.
92pub type PassResource = LoadedResource<PassMeta>;
93
94/// The loaded texture resource for a shader preset.
95pub type TextureResource = LoadedResource<TextureMeta>;
96
97/// A fully loaded-in-memory shader preset, with all paths resolved to data.
98#[derive(Debug, Clone)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub struct ShaderPresetPack {
101    /// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used.
102    /// Otherwise, the FBO after pass #N is passed a texture to next frame
103    #[cfg(feature = "parse_legacy_glsl")]
104    pub feedback_pass: i32,
105
106    /// The number of shaders enabled in the filter chain.
107    pub pass_count: i32,
108    // Everything is in Vecs because the expect number of values is well below 64.
109    /// Preset information for each shader.
110    pub passes: Vec<PassResource>,
111
112    /// Preset information for each texture.
113    pub textures: Vec<TextureResource>,
114
115    /// Preset information for each user parameter.
116    pub parameters: Vec<ParameterMeta>,
117}
118
119impl ShaderPresetPack {
120    /// Load a `ShaderPack` from a [`ShaderPreset`].
121    pub fn load_from_preset<E>(preset: ShaderPreset) -> Result<ShaderPresetPack, E>
122    where
123        E: From<PreprocessError>,
124        E: From<ImageError>,
125        E: Send,
126    {
127        #[cfg(not(target_arch = "wasm32"))]
128        let shaders_iter = preset.passes.into_par_iter();
129
130        #[cfg(target_arch = "wasm32")]
131        let shaders_iter = preset.shaders.into_iter();
132
133        #[cfg(not(target_arch = "wasm32"))]
134        let textures_iter = preset.textures.into_par_iter();
135
136        #[cfg(target_arch = "wasm32")]
137        let textures_iter = preset.textures.into_iter();
138
139        Ok(ShaderPresetPack {
140            #[cfg(feature = "parse_legacy_glsl")]
141            feedback_pass: preset.feedback_pass,
142
143            pass_count: preset.pass_count,
144            passes: shaders_iter
145                .map(|v| {
146                    Ok::<_, E>(PassResource {
147                        data: PassMeta::load(v.path.as_path(), preset.features)?,
148                        meta: v.meta,
149                    })
150                })
151                .collect::<Result<Vec<_>, _>>()?,
152            textures: textures_iter
153                .into_par_iter()
154                .map(|t| {
155                    Ok::<_, E>(TextureResource {
156                        data: TextureMeta::load(t.path.as_path(), ())?,
157                        meta: t.meta,
158                    })
159                })
160                .collect::<Result<Vec<_>, _>>()?,
161            parameters: preset.parameters,
162        })
163    }
164}
165
166#[cfg(feature = "serde")]
167mod serde_base64_or_bytes {
168    use base64::display::Base64Display;
169    use base64::engine::general_purpose::STANDARD;
170    use base64::Engine;
171    use serde::{Deserializer, Serializer};
172
173    #[allow(clippy::ptr_arg)]
174    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
175        if s.is_human_readable() {
176            s.collect_str(&Base64Display::new(v, &STANDARD))
177        } else {
178            serde_bytes::serialize(v, s)
179        }
180    }
181
182    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
183        if d.is_human_readable() {
184            struct Base64Visitor;
185            impl<'de> serde::de::Visitor<'de> for Base64Visitor {
186                type Value = Vec<u8>;
187
188                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
189                    formatter.write_str("a base64 encoded string")
190                }
191
192                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
193                where
194                    E: serde::de::Error,
195                {
196                    self.visit_bytes(v.as_ref())
197                }
198
199                fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
200                where
201                    E: serde::de::Error,
202                {
203                    self.visit_bytes(v.as_ref())
204                }
205
206                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
207                where
208                    E: serde::de::Error,
209                {
210                    STANDARD.decode(v).map_err(serde::de::Error::custom)
211                }
212
213                fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
214                where
215                    E: serde::de::Error,
216                {
217                    self.visit_bytes(v.as_ref())
218                }
219            }
220
221            d.deserialize_str(Base64Visitor)
222        } else {
223            serde_bytes::deserialize(d)
224        }
225    }
226}
227
228#[cfg(test)]
229mod test {
230    use crate::ShaderPresetPack;
231    use librashader_presets::{ShaderFeatures, ShaderPreset};
232    use std::fs::File;
233    use std::io::Write;
234
235    #[test]
236    fn test() {
237        let preset = ShaderPreset::try_parse(
238            "../test/shaders_slang/crt/crt-royale.slangp",
239            ShaderFeatures::NONE,
240        )
241        .unwrap();
242        let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
243        let mut file = File::create("crt-royale.slangpack.json").unwrap();
244        file.write_all(serde_json::to_vec_pretty(&resolved).unwrap().as_ref())
245            .unwrap();
246    }
247
248    #[test]
249    fn test_rmp() {
250        let preset = ShaderPreset::try_parse(
251            "../test/shaders_slang/crt/crt-royale.slangp",
252            ShaderFeatures::NONE,
253        )
254        .unwrap();
255        let resolved = ShaderPresetPack::load_from_preset::<anyhow::Error>(preset).unwrap();
256        let mut file = File::create("crt-royale.slangpack").unwrap();
257        file.write_all(rmp_serde::to_vec(&resolved).unwrap().as_ref())
258            .unwrap();
259    }
260}