diff --git a/Cargo.lock b/Cargo.lock index 4e22f6e..08baf33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,12 +90,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -473,7 +467,7 @@ checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "clap", "heck", - "indexmap 2.5.0", + "indexmap", "log", "proc-macro2", "quote", @@ -513,19 +507,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.52.6", -] - [[package]] name = "clap" version = "4.3.0" @@ -547,7 +528,7 @@ dependencies = [ "anstyle", "bitflags 1.3.2", "clap_lex", - "strsim 0.10.0", + "strsim", ] [[package]] @@ -858,57 +839,12 @@ dependencies = [ "windows 0.58.0", ] -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.79", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.79", -] - [[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - [[package]] name = "digest" version = "0.10.7" @@ -1377,12 +1313,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hexf-parse" version = "0.2.1" @@ -1395,29 +1325,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icrate" version = "0.0.4" @@ -1429,12 +1336,6 @@ dependencies = [ "objc2 0.4.1", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "image" version = "0.25.2" @@ -1475,17 +1376,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.5.0" @@ -1494,7 +1384,6 @@ checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", - "serde", ] [[package]] @@ -1634,6 +1523,7 @@ dependencies = [ "halfbrown", "librashader-cache", "librashader-common", + "librashader-pack", "librashader-preprocess", "librashader-presets", "librashader-reflect", @@ -1746,14 +1636,16 @@ name = "librashader-pack" version = "0.4.5" dependencies = [ "anyhow", + "base64 0.22.1", "image", "librashader-preprocess", "librashader-presets", "librashader-reflect", "rayon", + "rmp-serde", "serde", + "serde_bytes", "serde_json", - "serde_with", "thiserror", ] @@ -2100,7 +1992,7 @@ dependencies = [ "cfg_aliases", "codespan-reporting", "hexf-parse", - "indexmap 2.5.0", + "indexmap", "log", "petgraph", "pp-rs", @@ -2195,12 +2087,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-derive" version = "0.4.2" @@ -2567,7 +2453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap", ] [[package]] @@ -2631,12 +2517,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "pp-rs" version = "0.2.1" @@ -2862,6 +2742,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "ron" version = "0.7.1" @@ -2967,6 +2869,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -2999,36 +2910,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.5.0", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.79", -] - [[package]] name = "sha2" version = "0.10.8" @@ -3262,12 +3143,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "syn" version = "1.0.109" @@ -3349,37 +3224,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -3441,7 +3285,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -3800,7 +3644,7 @@ dependencies = [ "bitflags 2.6.0", "cfg_aliases", "document-features", - "indexmap 2.5.0", + "indexmap", "log", "naga", "once_cell", diff --git a/librashader-pack/Cargo.toml b/librashader-pack/Cargo.toml index 2256ec0..e64a436 100644 --- a/librashader-pack/Cargo.toml +++ b/librashader-pack/Cargo.toml @@ -9,13 +9,20 @@ librashader-preprocess = { path = "../librashader-preprocess", version = "0.4.5" librashader-reflect= { path = "../librashader-reflect", version = "0.4.5" } thiserror = "1.0.64" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.128" +serde = { version = "1.0", features = ["derive"], optional = true } rayon = { workspace = true } -image = { workspace = true} +image = {workspace = true} +base64 = { version = "0.22.1", optional = true } +serde_bytes = { version = "0.11.15", optional = true } [features] parse_legacy_glsl = ["librashader-presets/parse_legacy_glsl"] +serde = ["dep:serde", "dep:base64", "dep:serde_bytes"] [target.'cfg(not(target_arch="wasm32"))'.dependencies] -rayon = { workspace = true} \ No newline at end of file +rayon = { workspace = true } + +[dev-dependencies] +anyhow = "1.0.89" +serde_json = "1.0.128" +rmp-serde = "1.3.0" \ No newline at end of file diff --git a/librashader-pack/src/lib.rs b/librashader-pack/src/lib.rs index 8b13789..7f406a1 100644 --- a/librashader-pack/src/lib.rs +++ b/librashader-pack/src/lib.rs @@ -1 +1,248 @@ +//! Shader preset resource handling for librashader. +//! +//! This crate contains facilities to load shader preset resources from a [`ShaderPreset`]. +//! +//! Also defines abstractly the `.slangpack` shader format implemented via serde derives on [`ShaderPresetPack`]. +//! +use image::{ImageError, RgbaImage}; +use librashader_preprocess::{PreprocessError, ShaderSource}; +use librashader_presets::{ParameterMeta, ShaderPassMeta, ShaderPreset, TextureMeta}; +use std::path::Path; +#[cfg(not(target_arch = "wasm32"))] +use rayon::prelude::*; + +/// A buffer holding RGBA image bytes. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TextureBuffer { + #[cfg_attr(feature = "serde", serde(with = "serde_base64_or_bytes"))] + image: Vec, + width: u32, + height: u32, +} + +impl From for Option { + fn from(value: TextureBuffer) -> Self { + RgbaImage::from_raw(value.width, value.height, value.image) + } +} + +impl AsRef<[u8]> for TextureBuffer { + fn as_ref(&self) -> &[u8] { + self.image.as_ref() + } +} + +impl From for TextureBuffer { + fn from(value: RgbaImage) -> Self { + let width = value.width(); + let height = value.height(); + TextureBuffer { + image: value.into_raw(), + width, + height, + } + } +} + +/// A resource for a shader preset, fully loaded into memory. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ShaderPresetResource { + /// The fully qualified path to the texture. + pub data: M::ResourceType, + /// Meta information about the texture. + pub meta: M, +} + +/// Trait for a resource that is loadable from disk. +pub trait LoadableResource { + /// The type of the resource. + type ResourceType; + /// The error type when loading the resource. + type Error; + /// Load the resource from the path. + fn load(path: &Path) -> Result; +} + +impl LoadableResource for ShaderPassMeta { + type ResourceType = ShaderSource; + type Error = PreprocessError; + + fn load(path: &Path) -> Result { + ShaderSource::load(path) + } +} + +impl LoadableResource for TextureMeta { + type ResourceType = TextureBuffer; + type Error = ImageError; + + fn load(path: &Path) -> Result { + image::open(path).map(|img| TextureBuffer::from(img.to_rgba8())) + } +} + +/// The configuration for a single shader pass. +pub type ShaderPassData = ShaderPresetResource; +pub type TextureData = ShaderPresetResource; + +/// A shader preset, not reliant on disk, with all information needed. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ShaderPresetPack { + /// Used in legacy GLSL shader semantics. If < 0, no feedback pass is used. + /// Otherwise, the FBO after pass #N is passed a texture to next frame + #[cfg(feature = "parse_legacy_glsl")] + pub feedback_pass: i32, + + /// The number of shaders enabled in the filter chain. + pub shader_count: i32, + // Everything is in Vecs because the expect number of values is well below 64. + /// Preset information for each shader. + pub shaders: Vec, + + /// Preset information for each texture. + pub textures: Vec, + + /// Preset information for each user parameter. + pub parameters: Vec, +} + +impl ShaderPresetPack { + /// Load a `ShaderPack` from a [`ShaderPreset`]. + pub fn load_from_preset(preset: ShaderPreset) -> Result + where + E: From, + E: From, + E: Send, + { + #[cfg(not(target_arch = "wasm32"))] + let shaders_iter = preset.shaders.into_par_iter(); + + #[cfg(target_arch = "wasm32")] + let shaders_iter = preset.shaders.into_iter(); + + #[cfg(not(target_arch = "wasm32"))] + let textures_iter = preset.textures.into_par_iter(); + + #[cfg(target_arch = "wasm32")] + let textures_iter = preset.textures.into_iter(); + + Ok(ShaderPresetPack { + #[cfg(feature = "parse_legacy_glsl")] + feedback_pass: preset.feedback_pass, + + shader_count: preset.shader_count, + shaders: shaders_iter + .map(|v| { + Ok::<_, E>(ShaderPassData { + data: ShaderPassMeta::load(v.path.as_path())?, + meta: v.meta, + }) + }) + .collect::, _>>()?, + textures: textures_iter + .into_par_iter() + .map(|t| { + Ok::<_, E>(TextureData { + data: TextureMeta::load(t.path.as_path())?, + meta: t.meta, + }) + }) + .collect::, _>>()?, + parameters: preset.parameters, + }) + } +} + +#[cfg(feature = "serde")] +mod serde_base64_or_bytes { + use base64::display::Base64Display; + use base64::Engine; + use base64::engine::general_purpose::STANDARD; + use serde::{Deserializer, Serializer}; + + #[allow(clippy::ptr_arg)] + pub fn serialize(v: &Vec, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&Base64Display::new(v, &STANDARD)) + } else { + serde_bytes::serialize(v, s) + } + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { + if d.is_human_readable() { + struct Base64Visitor; + impl<'de> serde::de::Visitor<'de> for Base64Visitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a base64 encoded string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + self.visit_bytes(v.as_ref()) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + self.visit_bytes(v.as_ref()) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + STANDARD.decode(v).map_err(serde::de::Error::custom) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + self.visit_bytes(v.as_ref()) + } + } + + d.deserialize_str(Base64Visitor) + } else { + serde_bytes::deserialize(d) + } + } +} + +#[cfg(test)] +mod test { + use crate::ShaderPresetPack; + use librashader_presets::ShaderPreset; + use std::fs::File; + use std::io::Write; + + #[test] + fn test() { + let preset = + ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap(); + let resolved = ShaderPresetPack::load_from_preset::(preset).unwrap(); + let mut file = File::create("crt-royale.slangpack.json").unwrap(); + file.write_all(serde_json::to_vec_pretty(&resolved).unwrap().as_ref()) + .unwrap(); + } + + #[test] + fn test_rmp() { + let preset = + ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap(); + let resolved = ShaderPresetPack::load_from_preset::(preset).unwrap(); + let mut file = File::create("crt-royale.slangpack").unwrap(); + file.write_all(rmp_serde::to_vec(&resolved).unwrap().as_ref()) + .unwrap(); + } +} diff --git a/librashader-runtime/src/image.rs b/librashader-runtime/src/image.rs index 9514f1e..9794a40 100644 --- a/librashader-runtime/src/image.rs +++ b/librashader-runtime/src/image.rs @@ -2,7 +2,6 @@ pub use image::ImageError; use librashader_common::Size; use std::marker::PhantomData; -use image::RgbaImage; use std::path::Path; /// An uncompressed raw image ready to upload to GPU buffers. diff --git a/librashader/Cargo.toml b/librashader/Cargo.toml index 22bdaf1..49a89bf 100644 --- a/librashader/Cargo.toml +++ b/librashader/Cargo.toml @@ -19,6 +19,7 @@ librashader-preprocess = { path = "../librashader-preprocess", version = "0.4.5" librashader-reflect = { path = "../librashader-reflect", version = "0.4.5" } librashader-cache = { path = "../librashader-cache", version = "0.4.5" } librashader-runtime = { path = "../librashader-runtime", version = "0.4.5" } +librashader-pack = { path = "../librashader-pack", version = "0.4.5" } librashader-runtime-d3d11 = { path = "../librashader-runtime-d3d11", version = "0.4.5", optional = true } librashader-runtime-d3d12 = { path = "../librashader-runtime-d3d12", version = "0.4.5", optional = true } librashader-runtime-d3d9 = { path = "../librashader-runtime-d3d9", version = "0.4.5", optional = true } @@ -84,7 +85,7 @@ full = ["runtime-all", "reflect-all", "preprocess", "presets"] # cache hack docsrs = ["librashader-cache/docsrs"] -serde = ["librashader-presets/serde", "librashader-preprocess/serde", "librashader-reflect/serde"] +serde = ["librashader-presets/serde", "librashader-preprocess/serde", "librashader-reflect/serde", "librashader-pack/serde"] # emits warning messages in tests github-ci = [] diff --git a/librashader/src/lib.rs b/librashader/src/lib.rs index d4e7e3e..6c8a0c6 100644 --- a/librashader/src/lib.rs +++ b/librashader/src/lib.rs @@ -69,6 +69,9 @@ pub use librashader_common::map::ShortString; pub mod presets { use librashader_preprocess::{PreprocessError, ShaderParameter, ShaderSource}; pub use librashader_presets::*; + + pub use librashader_pack::*; + /// Get full parameter metadata from a shader preset. pub fn get_parameter_meta( preset: &ShaderPreset,