Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
b648892090 | |||
e522421df2 | |||
cb0a0920e2 | |||
34b54b18e1 | |||
b00f1f92f4 | |||
fc092d5e5b | |||
c86d39bf5a |
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -7,3 +7,6 @@
|
|||
[submodule "test/shaders_slang"]
|
||||
path = test/shaders_slang
|
||||
url = https://github.com/libretro/slang-shaders
|
||||
[submodule "test/quark-shaders"]
|
||||
path = test/quark-shaders
|
||||
url = https://github.com/hizzlekizzle/quark-shaders
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/bml" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/test/quark-shaders" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/test/shaders_slang" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
173
Cargo.lock
generated
173
Cargo.lock
generated
|
@ -197,6 +197,19 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bml"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f47cd661ced9603345d776f1b97350aa728602fcf9396583438935e3ea606f3b"
|
||||
dependencies = [
|
||||
"ordered-multimap",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"smartstring",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.0"
|
||||
|
@ -388,6 +401,28 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"proc-macro-hack",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.2.4"
|
||||
|
@ -447,6 +482,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
|
@ -522,6 +566,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
@ -620,6 +670,15 @@ dependencies = [
|
|||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d529fd73d344663edfd598ccb3f344e46034db51ebd103518eae34338248ad73"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
|
@ -936,13 +995,19 @@ dependencies = [
|
|||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1005,7 +1070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1212,6 +1277,7 @@ dependencies = [
|
|||
name = "librashader-presets"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"bml",
|
||||
"glob",
|
||||
"librashader-common 0.1.3",
|
||||
"nom",
|
||||
|
@ -1719,6 +1785,16 @@ version = "1.17.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.4.1"
|
||||
|
@ -1760,6 +1836,50 @@ version = "2.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.3"
|
||||
|
@ -1840,6 +1960,12 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
|
@ -2096,6 +2222,17 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shaderc"
|
||||
version = "0.8.2"
|
||||
|
@ -2132,6 +2269,17 @@ version = "1.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.16.0"
|
||||
|
@ -2182,6 +2330,12 @@ dependencies = [
|
|||
"cmake",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -2271,6 +2425,15 @@ dependencies = [
|
|||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.7.0"
|
||||
|
@ -2328,6 +2491,12 @@ version = "1.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
|
|
|
@ -12,7 +12,7 @@ members = [
|
|||
"librashader-runtime-vk",
|
||||
"librashader-cache",
|
||||
"librashader-capi",
|
||||
"librashader-build-script"
|
||||
"librashader-build-script",
|
||||
]
|
||||
|
||||
[workspace.metadata.release]
|
||||
|
|
|
@ -5,4 +5,4 @@ pub fn main() {
|
|||
println!("cargo:rustc-link-arg=/DELAYLOAD:dxcompiler.dll");
|
||||
println!("cargo:rustc-link-arg=/DELAYLOAD:d3d12.dll");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,10 @@ impl FromStr for WrapMode {
|
|||
"clamp_to_edge" => WrapMode::ClampToEdge,
|
||||
"repeat" => WrapMode::Repeat,
|
||||
"mirrored_repeat" => WrapMode::MirroredRepeat,
|
||||
|
||||
// quark
|
||||
"border" => WrapMode::ClampToBorder,
|
||||
"edge" => WrapMode::ClampToEdge,
|
||||
_ => WrapMode::ClampToBorder,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ nom_locate = "4.0.0"
|
|||
librashader-common = { path = "../librashader-common", version = "0.1.3" }
|
||||
num-traits = "0.2"
|
||||
|
||||
bml = "0.3.1"
|
||||
|
||||
[features]
|
||||
parse_legacy_glsl = []
|
||||
|
||||
|
|
|
@ -30,6 +30,9 @@ pub enum ParsePresetError {
|
|||
/// The shader preset did not contain valid UTF-8 bytes.
|
||||
#[error("expected utf8 bytes but got invalid utf8")]
|
||||
Utf8Error(Vec<u8>),
|
||||
/// Error parsing BML file.
|
||||
#[error("error parsing quark bml")]
|
||||
BmlError(#[from] bml::BmlError),
|
||||
}
|
||||
|
||||
/// The kind of error that may occur in parsing.
|
||||
|
|
|
@ -12,5 +12,12 @@
|
|||
mod error;
|
||||
mod parse;
|
||||
mod preset;
|
||||
mod quark;
|
||||
mod slang;
|
||||
|
||||
pub use error::*;
|
||||
pub use preset::*;
|
||||
|
||||
pub(crate) fn remove_if<T>(values: &mut Vec<T>, f: impl FnMut(&T) -> bool) -> Option<T> {
|
||||
values.iter().position(f).map(|idx| values.remove(idx))
|
||||
}
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
use std::path::Path;
|
||||
|
||||
use nom_locate::LocatedSpan;
|
||||
use std::str;
|
||||
|
||||
mod preset;
|
||||
mod token;
|
||||
mod value;
|
||||
|
||||
pub(crate) type Span<'a> = LocatedSpan<&'a str>;
|
||||
pub(crate) use token::Token;
|
||||
pub(crate) use value::ShaderStage;
|
||||
pub(crate) use value::ShaderType;
|
||||
pub(crate) use value::Value;
|
||||
|
||||
use crate::error::ParsePresetError;
|
||||
use crate::parse::preset::resolve_values;
|
||||
use crate::parse::value::parse_preset;
|
||||
use crate::slang::parse_preset;
|
||||
use crate::ShaderPreset;
|
||||
|
||||
pub(crate) fn remove_if<T>(values: &mut Vec<T>, f: impl FnMut(&T) -> bool) -> Option<T> {
|
||||
values.iter().position(f).map(|idx| values.remove(idx))
|
||||
}
|
||||
use value::resolve_values;
|
||||
|
||||
impl ShaderPreset {
|
||||
/// Try to parse the shader preset at the given path.
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
use crate::parse::remove_if;
|
||||
use crate::parse::value::Value;
|
||||
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
|
||||
|
||||
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
||||
let textures: Vec<TextureConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Texture { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Texture {
|
||||
name,
|
||||
filter_mode,
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
} = value
|
||||
{
|
||||
TextureConfig {
|
||||
name,
|
||||
path,
|
||||
wrap_mode,
|
||||
filter_mode,
|
||||
mipmap,
|
||||
}
|
||||
} else {
|
||||
unreachable!("values should all be of type Texture")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let parameters: Vec<ParameterConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Parameter { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Parameter(name, value) = value {
|
||||
ParameterConfig { name, value }
|
||||
} else {
|
||||
unreachable!("values should be all of type parameters")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut shaders = Vec::new();
|
||||
let shader_count =
|
||||
remove_if(&mut values, |v| matches!(*v, Value::ShaderCount(_))).map_or(0, |value| {
|
||||
if let Value::ShaderCount(count) = value {
|
||||
count
|
||||
} else {
|
||||
unreachable!("value should be of type shader_count")
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
let feedback_pass = remove_if(&mut values, |v| matches!(*v, Value::FeedbackPass(_)))
|
||||
.map(|value| {
|
||||
if let Value::FeedbackPass(pass) = value {
|
||||
pass
|
||||
} else {
|
||||
unreachable!("value should be of type feedback_pass")
|
||||
}
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
for shader in 0..shader_count {
|
||||
if let Some(Value::Shader(id, name)) = remove_if(
|
||||
&mut values,
|
||||
|v| matches!(*v, Value::Shader(shader_index, _) if shader_index == shader),
|
||||
) {
|
||||
let shader_values: Vec<Value> = values
|
||||
.drain_filter(|v| v.shader_index() == Some(shader))
|
||||
.collect();
|
||||
let scale_type = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleType(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut scale_type_x = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleTypeX(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut scale_type_y = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleTypeY(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if scale_type.is_some() {
|
||||
// scale takes priority
|
||||
// https://github.com/libretro/RetroArch/blob/fcbd72dbf3579eb31721fbbf0d89a139834bcce9/gfx/video_shader_parse.c#L310
|
||||
scale_type_x = scale_type;
|
||||
scale_type_y = scale_type;
|
||||
}
|
||||
|
||||
let scale_valid = scale_type_x.is_some() || scale_type_y.is_some();
|
||||
|
||||
let scale = shader_values.iter().find_map(|f| match f {
|
||||
Value::Scale(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut scale_x = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleX(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut scale_y = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleY(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if scale.is_some() {
|
||||
// scale takes priority
|
||||
// https://github.com/libretro/RetroArch/blob/fcbd72dbf3579eb31721fbbf0d89a139834bcce9/gfx/video_shader_parse.c#L310
|
||||
scale_x = scale;
|
||||
scale_y = scale;
|
||||
}
|
||||
|
||||
let shader = ShaderPassConfig {
|
||||
id,
|
||||
name,
|
||||
alias: shader_values.iter().find_map(|f| match f {
|
||||
Value::Alias(_, value) => Some(value.to_string()),
|
||||
_ => None,
|
||||
}),
|
||||
filter: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FilterMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
wrap_mode: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::WrapMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
frame_count_mod: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FrameCountMod(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0),
|
||||
srgb_framebuffer: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::SrgbFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
float_framebuffer: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FloatFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
mipmap_input: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::MipmapInput(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
scaling: Scale2D {
|
||||
valid: scale_valid,
|
||||
x: Scaling {
|
||||
scale_type: scale_type_x.unwrap_or_default(),
|
||||
factor: scale_x.unwrap_or_default(),
|
||||
},
|
||||
y: Scaling {
|
||||
scale_type: scale_type_y.unwrap_or_default(),
|
||||
factor: scale_y.unwrap_or_default(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
shaders.push(shader)
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPreset {
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
feedback_pass,
|
||||
shader_count,
|
||||
shaders,
|
||||
textures,
|
||||
parameters,
|
||||
}
|
||||
}
|
|
@ -1,26 +1,28 @@
|
|||
use crate::error::{ParseErrorKind, ParsePresetError};
|
||||
use crate::parse::{remove_if, Span, Token};
|
||||
use crate::{ScaleFactor, ScaleType};
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::digit1;
|
||||
use nom::combinator::{eof, map_res};
|
||||
use std::collections::VecDeque;
|
||||
use crate::{
|
||||
remove_if, ParameterConfig, Scale2D, ScaleFactor, ScaleType, Scaling, ShaderPassConfig,
|
||||
ShaderPath, ShaderPreset, TextureConfig,
|
||||
};
|
||||
use librashader_common::{FilterMode, ImageFormat, WrapMode};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nom::IResult;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
#[derive(Debug)]
|
||||
pub enum ShaderStage {
|
||||
Fragment,
|
||||
Vertex,
|
||||
Geometry,
|
||||
}
|
||||
|
||||
use crate::parse::token::do_lex;
|
||||
use librashader_common::{FilterMode, WrapMode};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
#[derive(Debug)]
|
||||
pub enum ShaderType {
|
||||
Slang,
|
||||
Quark(ShaderStage),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
ShaderCount(i32),
|
||||
FeedbackPass(i32),
|
||||
Shader(i32, PathBuf),
|
||||
Shader(i32, ShaderType, PathBuf),
|
||||
ScaleX(i32, ScaleFactor),
|
||||
ScaleY(i32, ScaleFactor),
|
||||
Scale(i32, ScaleFactor),
|
||||
|
@ -35,6 +37,7 @@ pub enum Value {
|
|||
MipmapInput(i32, bool),
|
||||
Alias(i32, String),
|
||||
Parameter(String, f32),
|
||||
FormatOverride(i32, ImageFormat),
|
||||
Texture {
|
||||
name: String,
|
||||
filter_mode: FilterMode,
|
||||
|
@ -47,7 +50,7 @@ pub enum Value {
|
|||
impl Value {
|
||||
pub(crate) fn shader_index(&self) -> Option<i32> {
|
||||
match self {
|
||||
Value::Shader(i, _) => Some(*i),
|
||||
Value::Shader(i, _, _) => Some(*i),
|
||||
Value::ScaleX(i, _) => Some(*i),
|
||||
Value::ScaleY(i, _) => Some(*i),
|
||||
Value::Scale(i, _) => Some(*i),
|
||||
|
@ -61,546 +64,211 @@ impl Value {
|
|||
Value::SrgbFramebuffer(i, _) => Some(*i),
|
||||
Value::MipmapInput(i, _) => Some(*i),
|
||||
Value::Alias(i, _) => Some(*i),
|
||||
Value::FormatOverride(i, _) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_int(input: Span) -> Result<i32, ParsePresetError> {
|
||||
// Presets like to commit ✨CRIMES✨ and end their lines with a ";".
|
||||
// It's too hard to put this in the lexer because we can't tell between
|
||||
// semicolon crimes or a valid param/texture name listing.
|
||||
let to_parse = input.trim().trim_end_matches(";");
|
||||
i32::from_str(to_parse)
|
||||
.map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Int,
|
||||
})
|
||||
.or_else(|e| {
|
||||
// An even more egregious ✨CRIME✨ is using a float as a shader index.
|
||||
let result = f32::from_str(to_parse).map_err(|_| e)?;
|
||||
let result = result
|
||||
.trunc()
|
||||
.to_i32()
|
||||
.ok_or(ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Int,
|
||||
})?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_ul(input: Span) -> Result<u32, ParsePresetError> {
|
||||
// Presets like to commit ✨CRIMES✨ and end their lines with a ";".
|
||||
// It's too hard to put this in the lexer because we can't tell between
|
||||
// semicolon crimes or a valid param/texture name listing.
|
||||
u32::from_str(input.trim().trim_end_matches(";")).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::UnsignedInt,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_float(input: Span) -> Result<f32, ParsePresetError> {
|
||||
// Presets like to commit ✨CRIMES✨ and end their lines with a ";".
|
||||
// It's too hard to put this in the lexer because we can't tell between
|
||||
// semicolon crimes or a valid param/texture name listing.
|
||||
f32::from_str(input.trim().trim_end_matches(";")).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Float,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_bool(input: Span) -> Result<bool, ParsePresetError> {
|
||||
if let Ok(i) = i32::from_str(input.trim()) {
|
||||
return match i {
|
||||
1 => Ok(true),
|
||||
0 => Ok(false),
|
||||
_ => Err(ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Bool,
|
||||
}),
|
||||
};
|
||||
}
|
||||
bool::from_str(input.trim()).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Bool,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_indexed_key<'a>(key: &'static str, input: Span<'a>) -> IResult<Span<'a>, i32> {
|
||||
let (input, _) = tag(key)(input)?;
|
||||
let (input, idx) = map_res(digit1, from_int)(input)?;
|
||||
let (input, _) = eof(input)?;
|
||||
Ok((input, idx))
|
||||
}
|
||||
|
||||
pub const SHADER_MAX_REFERENCE_DEPTH: usize = 16;
|
||||
|
||||
fn load_child_reference_strings(
|
||||
root_references: Vec<PathBuf>,
|
||||
root_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<(PathBuf, String)>, ParsePresetError> {
|
||||
let root_path = root_path.as_ref();
|
||||
|
||||
let mut reference_depth = 0;
|
||||
let mut reference_strings: VecDeque<(PathBuf, String)> = VecDeque::new();
|
||||
let root_references = vec![(root_path.to_path_buf(), root_references)];
|
||||
let mut root_references = VecDeque::from(root_references);
|
||||
// search needs to be depth first to allow for overrides.
|
||||
while let Some((reference_root, referenced_paths)) = root_references.pop_front() {
|
||||
if reference_depth > SHADER_MAX_REFERENCE_DEPTH {
|
||||
return Err(ParsePresetError::ExceededReferenceDepth);
|
||||
}
|
||||
// enter the current root
|
||||
reference_depth += 1;
|
||||
// canonicalize current root
|
||||
let reference_root = reference_root
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(reference_root.to_path_buf(), e))?;
|
||||
|
||||
// resolve all referenced paths against root
|
||||
// println!("Resolving {referenced_paths:?} against {reference_root:?}.");
|
||||
|
||||
for path in referenced_paths {
|
||||
let mut path = reference_root
|
||||
.join(path.clone())
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
|
||||
// println!("Opening {:?}", path);
|
||||
let mut reference_contents = String::new();
|
||||
File::open(&path)
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?
|
||||
.read_to_string(&mut reference_contents)
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
|
||||
|
||||
let mut new_tokens = do_lex(&reference_contents)?;
|
||||
let new_references: Vec<PathBuf> = new_tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
path.pop();
|
||||
reference_strings.push_front((path.clone(), reference_contents));
|
||||
if !new_references.is_empty() {
|
||||
root_references.push_front((path, new_references));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(reference_strings.into())
|
||||
}
|
||||
|
||||
pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let path = path.as_ref();
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
let mut contents = String::new();
|
||||
File::open(&path)
|
||||
.and_then(|mut f| f.read_to_string(&mut contents))
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
let tokens = super::token::do_lex(&contents)?;
|
||||
parse_values(tokens, path)
|
||||
}
|
||||
|
||||
pub fn parse_values(
|
||||
mut tokens: Vec<Token>,
|
||||
root_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let mut root_path = root_path.as_ref().to_path_buf();
|
||||
if root_path.is_relative() {
|
||||
return Err(ParsePresetError::RootPathWasNotAbsolute);
|
||||
}
|
||||
if !root_path.is_dir() {
|
||||
// we don't really care if this doesn't do anything because a non-canonical root path will
|
||||
// fail at a later stage during resolution.
|
||||
root_path.pop();
|
||||
}
|
||||
|
||||
let references: Vec<PathBuf> = tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
// unfortunately we need to lex twice because there's no way to know the references ahead of time.
|
||||
let child_strings = load_child_reference_strings(references, &root_path)?;
|
||||
let mut all_tokens: Vec<(&Path, Vec<Token>)> = Vec::new();
|
||||
|
||||
for (path, string) in child_strings.iter() {
|
||||
// lex the child tokens
|
||||
let mut tokens = do_lex(string.as_ref())?;
|
||||
tokens.retain(|token| *token.key.fragment() != "#reference");
|
||||
all_tokens.push((path.as_path(), tokens))
|
||||
}
|
||||
|
||||
// load depth first, so all child tokens are first.
|
||||
// Later tokens take precedence.
|
||||
all_tokens.push((root_path.as_path(), tokens));
|
||||
// collect all possible parameter names.
|
||||
let mut parameter_names: Vec<&str> = Vec::new();
|
||||
for (_, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| *token.key.fragment() == "parameters") {
|
||||
let parameter_name_string: &str = token.value.fragment();
|
||||
for parameter_name in parameter_name_string.split(';') {
|
||||
parameter_names.push(parameter_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect all possible texture names.
|
||||
let mut texture_names: Vec<&str> = Vec::new();
|
||||
for (_, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| *token.key.fragment() == "textures") {
|
||||
let texture_name_string: &str = token.value.fragment();
|
||||
for texture_name in texture_name_string.split(';') {
|
||||
texture_names.push(texture_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut values = Vec::new();
|
||||
// resolve shader paths.
|
||||
for (path, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| parse_indexed_key("shader", token.key).is_ok()) {
|
||||
let (_, index) = parse_indexed_key("shader", token.key).map_err(|e| match e {
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => {
|
||||
let input: Span = e.input;
|
||||
ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Index("shader"),
|
||||
}
|
||||
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
||||
let textures: Vec<TextureConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Texture { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Texture {
|
||||
name,
|
||||
filter_mode,
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
} = value
|
||||
{
|
||||
TextureConfig {
|
||||
name,
|
||||
path,
|
||||
wrap_mode,
|
||||
filter_mode,
|
||||
mipmap,
|
||||
}
|
||||
_ => ParsePresetError::ParserError {
|
||||
offset: 0,
|
||||
row: 0,
|
||||
col: 0,
|
||||
kind: ParseErrorKind::Index("shader"),
|
||||
},
|
||||
})?;
|
||||
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(relative_path.clone(), e))?;
|
||||
values.push(Value::Shader(index, relative_path))
|
||||
}
|
||||
}
|
||||
|
||||
// resolve texture paths
|
||||
let mut textures = Vec::new();
|
||||
for (path, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| texture_names.contains(token.key.fragment())) {
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(relative_path.clone(), e))?;
|
||||
textures.push((token.key, relative_path))
|
||||
}
|
||||
}
|
||||
|
||||
let mut tokens: Vec<(&Path, Token)> = all_tokens
|
||||
.into_iter()
|
||||
.flat_map(|(p, token)| token.into_iter().map(move |t| (p, t)))
|
||||
} else {
|
||||
unreachable!("values should all be of type Texture")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let parameters: Vec<ParameterConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Parameter { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Parameter(name, value) = value {
|
||||
ParameterConfig { name, value }
|
||||
} else {
|
||||
unreachable!("values should be all of type parameters")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (texture, path) in textures {
|
||||
let mipmap = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_mipmap")
|
||||
&& t.key.len() == texture.len() + "_mipmap".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let linear = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_linear")
|
||||
&& t.key.len() == texture.len() + "_linear".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let wrap_mode = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& (t.key.ends_with("_wrap_mode") || t.key.ends_with("_repeat_mode"))
|
||||
&& (t.key.len() == texture.len() + "_wrap_mode".len()
|
||||
|| t.key.len() == texture.len() + "_repeat_mode".len())
|
||||
})
|
||||
// NOPANIC: infallible
|
||||
.map_or_else(WrapMode::default, |(_, v)| {
|
||||
WrapMode::from_str(&v.value).unwrap()
|
||||
let mut shaders = Vec::new();
|
||||
let shader_count =
|
||||
remove_if(&mut values, |v| matches!(*v, Value::ShaderCount(_))).map_or(0, |value| {
|
||||
if let Value::ShaderCount(count) = value {
|
||||
count
|
||||
} else {
|
||||
unreachable!("value should be of type shader_count")
|
||||
}
|
||||
});
|
||||
|
||||
// This really isn't supported but crt-torridgristle uses this syntax.
|
||||
// Again, don't know how this works in RA but RA's parser isn't as strict as ours.
|
||||
let filter = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with("filter_")
|
||||
&& t.key.ends_with(*texture)
|
||||
&& t.key.len() == "filter_".len() + texture.len()
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
let feedback_pass = remove_if(&mut values, |v| matches!(*v, Value::FeedbackPass(_)))
|
||||
.map(|value| {
|
||||
if let Value::FeedbackPass(pass) = value {
|
||||
pass
|
||||
} else {
|
||||
unreachable!("value should be of type feedback_pass")
|
||||
}
|
||||
})
|
||||
// NOPANIC: infallible
|
||||
.map_or(None, |(_, v)| Some(FilterMode::from_str(&v.value).unwrap()));
|
||||
.unwrap_or(0);
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: texture.to_string(),
|
||||
filter_mode: filter.unwrap_or(if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
FilterMode::Nearest
|
||||
}),
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
})
|
||||
}
|
||||
for shader in 0..shader_count {
|
||||
if let Some(Value::Shader(id, ShaderType::Slang, name)) = remove_if(
|
||||
&mut values,
|
||||
|v| matches!(*v, Value::Shader(shader_index, ShaderType::Slang, _) if shader_index == shader),
|
||||
) {
|
||||
let shader_values: Vec<Value> = values
|
||||
.drain_filter(|v| v.shader_index() == Some(shader))
|
||||
.collect();
|
||||
let scale_type = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleType(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mut rest_tokens = Vec::new();
|
||||
// hopefully no more textures left in the token tree
|
||||
for (p, token) in tokens {
|
||||
if parameter_names.contains(token.key.fragment()) {
|
||||
let param_val = from_float(token.value)
|
||||
// This is literally just to work around BEAM_PROFILE in crt-hyllian-sinc-glow.slangp
|
||||
// which has ""0'.000000". This somehow works in RA because it defaults to 0, probably.
|
||||
// This hack is only used for **known** parameter names. If we tried this for undeclared
|
||||
// params (god help me), it would be pretty bad because we lose texture path fallback.
|
||||
.unwrap_or(0.0);
|
||||
values.push(Value::Parameter(
|
||||
token.key.fragment().to_string(),
|
||||
param_val,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
if token.key.fragment() == &"shaders" {
|
||||
let shader_count = from_int(token.value)?;
|
||||
values.push(Value::ShaderCount(shader_count));
|
||||
continue;
|
||||
}
|
||||
if token.key.fragment() == &"feedback_pass" {
|
||||
let feedback_pass = from_int(token.value)?;
|
||||
values.push(Value::FeedbackPass(feedback_pass));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("filter_linear", token.key) {
|
||||
let linear = from_bool(token.value)?;
|
||||
values.push(Value::FilterMode(
|
||||
idx,
|
||||
if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
FilterMode::Nearest
|
||||
},
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let mut scale_type_x = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleTypeX(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("wrap_mode", token.key) {
|
||||
let wrap_mode = WrapMode::from_str(&token.value).unwrap();
|
||||
values.push(Value::WrapMode(idx, wrap_mode));
|
||||
continue;
|
||||
}
|
||||
let mut scale_type_y = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleTypeY(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// crt-geom uses repeat_mode...
|
||||
if let Ok((_, idx)) = parse_indexed_key("repeat_mode", token.key) {
|
||||
let wrap_mode = WrapMode::from_str(&token.value).unwrap();
|
||||
values.push(Value::WrapMode(idx, wrap_mode));
|
||||
continue;
|
||||
}
|
||||
if scale_type.is_some() {
|
||||
// scale takes priority
|
||||
// https://github.com/libretro/RetroArch/blob/fcbd72dbf3579eb31721fbbf0d89a139834bcce9/gfx/video_shader_parse.c#L310
|
||||
scale_type_x = scale_type;
|
||||
scale_type_y = scale_type;
|
||||
}
|
||||
|
||||
// crt-royale uses 'texture_wrap_mode' instead of 'wrap_mode', I have no idea
|
||||
// how this possibly could work in RA, but here it is..
|
||||
if let Ok((_, idx)) = parse_indexed_key("texture_wrap_mode", token.key) {
|
||||
let wrap_mode = WrapMode::from_str(&token.value).unwrap();
|
||||
values.push(Value::WrapMode(idx, wrap_mode));
|
||||
continue;
|
||||
}
|
||||
let scale_valid = scale_type_x.is_some() || scale_type_y.is_some();
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("frame_count_mod", token.key) {
|
||||
let frame_count_mod = from_ul(token.value)?;
|
||||
values.push(Value::FrameCountMod(idx, frame_count_mod));
|
||||
continue;
|
||||
}
|
||||
let scale = shader_values.iter().find_map(|f| match f {
|
||||
Value::Scale(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("srgb_framebuffer", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::SrgbFramebuffer(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
let mut scale_x = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleX(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("float_framebuffer", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::FloatFramebuffer(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
let mut scale_y = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleY(_, value) => Some(*value),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("mipmap_input", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::MipmapInput(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
if scale.is_some() {
|
||||
// scale takes priority
|
||||
// https://github.com/libretro/RetroArch/blob/fcbd72dbf3579eb31721fbbf0d89a139834bcce9/gfx/video_shader_parse.c#L310
|
||||
scale_x = scale;
|
||||
scale_y = scale;
|
||||
}
|
||||
|
||||
// vector-glow-alt-render.slangp uses "mipmap" for pass 1, but "mipmap_input" for everything else.
|
||||
if let Ok((_, idx)) = parse_indexed_key("mipmap", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::MipmapInput(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("alias", token.key) {
|
||||
values.push(Value::Alias(idx, token.value.to_string()));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type", token.key) {
|
||||
let scale_type = ScaleType::from_str(token.value.trim())?;
|
||||
values.push(Value::ScaleType(idx, scale_type));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type_x", token.key) {
|
||||
let scale_type = ScaleType::from_str(token.value.trim())?;
|
||||
values.push(Value::ScaleTypeX(idx, scale_type));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type_y", token.key) {
|
||||
let scale_type = ScaleType::from_str(token.value.trim())?;
|
||||
values.push(Value::ScaleTypeY(idx, scale_type));
|
||||
continue;
|
||||
}
|
||||
rest_tokens.push((p, token))
|
||||
}
|
||||
|
||||
let mut undeclared_textures = Vec::new();
|
||||
for (path, token) in &rest_tokens {
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale", token.key) {
|
||||
let scale = if values.iter().any(|t| matches!(*t, Value::ScaleType(match_idx, ScaleType::Absolute) if match_idx == idx)) {
|
||||
let scale = from_int(token.value)?;
|
||||
ScaleFactor::Absolute(scale)
|
||||
} else {
|
||||
let scale = from_float(token.value)?;
|
||||
ScaleFactor::Float(scale)
|
||||
};
|
||||
|
||||
values.push(Value::Scale(idx, scale));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_x", token.key) {
|
||||
let scale = if values.iter().any(|t| matches!(*t, Value::ScaleType(match_idx, ScaleType::Absolute) | Value::ScaleTypeX(match_idx, ScaleType::Absolute) if match_idx == idx)) {
|
||||
let scale = from_int(token.value)?;
|
||||
ScaleFactor::Absolute(scale)
|
||||
} else {
|
||||
let scale = from_float(token.value)?;
|
||||
ScaleFactor::Float(scale)
|
||||
};
|
||||
|
||||
values.push(Value::ScaleX(idx, scale));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_y", token.key) {
|
||||
let scale = if values.iter().any(|t| matches!(*t, Value::ScaleType(match_idx, ScaleType::Absolute) | Value::ScaleTypeY(match_idx, ScaleType::Absolute) if match_idx == idx)) {
|
||||
let scale = from_int(token.value)?;
|
||||
ScaleFactor::Absolute(scale)
|
||||
} else {
|
||||
let scale = from_float(token.value)?;
|
||||
ScaleFactor::Float(scale)
|
||||
};
|
||||
|
||||
values.push(Value::ScaleY(idx, scale));
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle undeclared parameters after parsing everything else as a last resort.
|
||||
if let Ok(param_val) = from_float(token.value) {
|
||||
values.push(Value::Parameter(
|
||||
token.key.fragment().to_string(),
|
||||
param_val,
|
||||
));
|
||||
}
|
||||
// very last resort, assume undeclared texture (must have extension)
|
||||
else if Path::new(token.value.fragment()).extension().is_some()
|
||||
&& ["_mipmap", "_linear", "_wrap_mode", "_repeat_mode"]
|
||||
let srgb_frambuffer = shader_values
|
||||
.iter()
|
||||
.all(|k| !token.key.ends_with(k))
|
||||
{
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(relative_path.clone(), e))?;
|
||||
undeclared_textures.push((token.key, relative_path));
|
||||
.find_map(|f| match f {
|
||||
Value::SrgbFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let float_framebuffer = shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FloatFramebuffer(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let framebuffer_format_override = shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FormatOverride(_, value) => Some(Some(*value)),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(if srgb_frambuffer {
|
||||
Some(ImageFormat::R8G8B8A8Srgb)
|
||||
} else if float_framebuffer {
|
||||
Some(ImageFormat::R16G16B16A16Sfloat)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
|
||||
let shader = ShaderPassConfig {
|
||||
id,
|
||||
source_path: ShaderPath::Slang(name),
|
||||
alias: shader_values.iter().find_map(|f| match f {
|
||||
Value::Alias(_, value) => Some(value.to_string()),
|
||||
_ => None,
|
||||
}),
|
||||
filter: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FilterMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
wrap_mode: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::WrapMode(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
frame_count_mod: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::FrameCountMod(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0),
|
||||
framebuffer_format_override,
|
||||
mipmap_input: shader_values
|
||||
.iter()
|
||||
.find_map(|f| match f {
|
||||
Value::MipmapInput(_, value) => Some(*value),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(false),
|
||||
scaling: Scale2D {
|
||||
valid: scale_valid,
|
||||
x: Scaling {
|
||||
scale_type: scale_type_x.unwrap_or_default(),
|
||||
factor: scale_x.unwrap_or_default(),
|
||||
},
|
||||
y: Scaling {
|
||||
scale_type: scale_type_y.unwrap_or_default(),
|
||||
factor: scale_y.unwrap_or_default(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
shaders.push(shader)
|
||||
}
|
||||
|
||||
// we tried our best
|
||||
}
|
||||
|
||||
// Since there are undeclared textures we need to deal with potential mipmap information.
|
||||
for (texture, path) in undeclared_textures {
|
||||
let mipmap = remove_if(&mut rest_tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_mipmap")
|
||||
&& t.key.len() == texture.len() + "_mipmap".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let linear = remove_if(&mut rest_tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_linear")
|
||||
&& t.key.len() == texture.len() + "_linear".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let wrap_mode = remove_if(&mut rest_tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& (t.key.ends_with("_wrap_mode") || t.key.ends_with("_repeat_mode"))
|
||||
&& (t.key.len() == texture.len() + "_wrap_mode".len()
|
||||
|| t.key.len() == texture.len() + "_repeat_mode".len())
|
||||
})
|
||||
// NOPANIC: infallible
|
||||
.map_or_else(WrapMode::default, |(_, v)| {
|
||||
WrapMode::from_str(&v.value).unwrap()
|
||||
});
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: texture.to_string(),
|
||||
filter_mode: if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
FilterMode::Nearest
|
||||
},
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
// all tokens should be ok to process now.
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parse::value::parse_preset;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
pub fn parse_basic() {
|
||||
let root =
|
||||
PathBuf::from("../test/slang-shaders/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
|
||||
let basic = parse_preset(root);
|
||||
eprintln!("{basic:?}");
|
||||
assert!(basic.is_ok());
|
||||
ShaderPreset {
|
||||
#[cfg(feature = "parse_legacy_glsl")]
|
||||
feedback_pass,
|
||||
shader_count,
|
||||
shaders,
|
||||
textures,
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ pub struct ShaderPassConfig {
|
|||
/// The index of the shader pass relative to its parent preset.
|
||||
pub id: i32,
|
||||
/// The fully qualified path to the shader pass source file.
|
||||
pub name: PathBuf,
|
||||
pub source_path: ShaderPath,
|
||||
/// The alias of the shader pass if available.
|
||||
pub alias: Option<String>,
|
||||
/// The filtering mode that this shader pass should expect.
|
||||
|
@ -19,10 +19,11 @@ pub struct ShaderPassConfig {
|
|||
pub wrap_mode: WrapMode,
|
||||
/// The number to which to wrap the frame count before passing it to the uniforms.
|
||||
pub frame_count_mod: u32,
|
||||
/// Whether or not this shader pass expects an SRGB framebuffer output.
|
||||
pub srgb_framebuffer: bool,
|
||||
/// Whether or not this shader pass expects an float framebuffer output.
|
||||
pub float_framebuffer: bool,
|
||||
/// If the preset overrides the framebuffer format, get the format override.
|
||||
///
|
||||
/// The precedence is always SRGB, then float. For quark shaders, this is
|
||||
/// specified in the shader manifest.
|
||||
pub framebuffer_format_override: Option<ImageFormat>,
|
||||
/// Whether or not to generate mipmaps for the input texture before passing to the shader.
|
||||
pub mipmap_input: bool,
|
||||
/// Specifies the scaling of the output framebuffer for this shader pass.
|
||||
|
@ -30,18 +31,6 @@ pub struct ShaderPassConfig {
|
|||
}
|
||||
|
||||
impl ShaderPassConfig {
|
||||
/// If the framebuffer expects a different format than what was defined in the
|
||||
/// shader source, returns such format.
|
||||
#[inline(always)]
|
||||
pub fn get_format_override(&self) -> Option<ImageFormat> {
|
||||
if self.srgb_framebuffer {
|
||||
return Some(ImageFormat::R8G8B8A8Srgb);
|
||||
} else if self.float_framebuffer {
|
||||
return Some(ImageFormat::R16G16B16A16Sfloat);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_frame_count(&self, count: usize) -> u32 {
|
||||
(if self.frame_count_mod > 0 {
|
||||
|
@ -52,6 +41,18 @@ impl ShaderPassConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// The path to a shader.
|
||||
pub enum ShaderPath {
|
||||
/// Slang combined shader
|
||||
Slang(PathBuf),
|
||||
/// Quark split vertex/fragment shaders.
|
||||
Quark {
|
||||
vertex: Option<PathBuf>,
|
||||
fragment: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
/// The scaling type for the shader pass.
|
||||
|
|
217
librashader-presets/src/quark/mod.rs
Normal file
217
librashader-presets/src/quark/mod.rs
Normal file
|
@ -0,0 +1,217 @@
|
|||
use std::f32;
|
||||
use crate::parse::{ShaderStage, ShaderType, Value};
|
||||
use crate::{ParseErrorKind, ParsePresetError, ScaleFactor, ScaleType};
|
||||
use bml::BmlNode;
|
||||
use librashader_common::{FilterMode, ImageFormat, WrapMode};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn parse_bml_node(path: impl AsRef<Path>) -> Result<BmlNode, ParsePresetError> {
|
||||
let path = path.as_ref();
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
let mut manifest_path = path.join("manifest.bml");
|
||||
let mut contents = String::new();
|
||||
File::open(&manifest_path)
|
||||
.and_then(|mut f| f.read_to_string(&mut contents))
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
// BML expects a newline.
|
||||
contents.push_str("\n");
|
||||
let contents = contents.to_string();
|
||||
Ok(bml::BmlNode::try_from(&*contents)?)
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn parse_scale(scale: &str) -> Result<(ScaleType, ScaleFactor), ParsePresetError> {
|
||||
if scale.ends_with("%") {
|
||||
let value = f32::from_str(scale.trim_end_matches("%"))
|
||||
.map_err(|_| {
|
||||
eprintln!("{scale}");
|
||||
ParsePresetError::ParserError {
|
||||
offset: 0,
|
||||
row: 0,
|
||||
col: 0,
|
||||
kind: ParseErrorKind::UnsignedInt,
|
||||
}
|
||||
})? as f32 / 100.0;
|
||||
|
||||
Ok((ScaleType::Input, ScaleFactor::Float(value)))
|
||||
} else {
|
||||
// allowed to end in " px"
|
||||
let value = i32::from_str(scale.trim_end_matches(" px"))
|
||||
.map_err(|_| {
|
||||
eprintln!("{scale}");
|
||||
ParsePresetError::ParserError {
|
||||
offset: 0,
|
||||
row: 0,
|
||||
col: 0,
|
||||
kind: ParseErrorKind::UnsignedInt,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok((ScaleType::Absolute, ScaleFactor::Absolute(value)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn parse_values(node: &BmlNode, root: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let mut values = Vec::new();
|
||||
|
||||
for (index, (name, program)) in node.nodes().enumerate() {
|
||||
eprintln!("{}, {:?}", name, program);
|
||||
|
||||
if let Some(filter) = program.named("filter").next() {
|
||||
// NOPANIC: infallible
|
||||
values.push(Value::FilterMode(
|
||||
index as i32,
|
||||
FilterMode::from_str(filter.value().trim()).unwrap(),
|
||||
))
|
||||
}
|
||||
if let Some(wrap) = program.named("wrap").next() {
|
||||
values.push(Value::WrapMode(
|
||||
index as i32,
|
||||
WrapMode::from_str(wrap.value().trim()).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(height) = program.named("height").next() {
|
||||
let height = height.value().trim();
|
||||
let (scale_type, factor) = parse_scale(height)?;
|
||||
values.push(Value::ScaleTypeY(
|
||||
index as i32,
|
||||
scale_type,
|
||||
));
|
||||
values.push(Value::ScaleY(
|
||||
index as i32,
|
||||
factor,
|
||||
))
|
||||
} else if name != "input" {
|
||||
values.push(Value::ScaleTypeY(
|
||||
index as i32,
|
||||
ScaleType::Viewport,
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(width) = program.named("width").next() {
|
||||
let width = width.value().trim();
|
||||
let (scale_type, factor) = parse_scale(width)?;
|
||||
values.push(Value::ScaleTypeY(
|
||||
index as i32,
|
||||
scale_type,
|
||||
));
|
||||
values.push(Value::ScaleY(
|
||||
index as i32,
|
||||
factor,
|
||||
))
|
||||
} else if name != "input" {
|
||||
values.push(Value::ScaleTypeY(
|
||||
index as i32,
|
||||
ScaleType::Viewport,
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(format) = program.named("format").next() {
|
||||
let format = match format.value() {
|
||||
"rgba8" => ImageFormat::R8G8B8A8Unorm,
|
||||
"rgb10a2" => ImageFormat::A2B10G10R10UnormPack32,
|
||||
"rgba16" => ImageFormat::R16G16B16A16Sint,
|
||||
"rgba16f" => ImageFormat::R16G16B16A16Sfloat,
|
||||
"rgba32f" => ImageFormat::R32G32B32A32Sfloat,
|
||||
|
||||
// srgb extension
|
||||
"srgb8" => ImageFormat::R8G8B8A8Srgb,
|
||||
|
||||
// don't support rgba12
|
||||
_ => ImageFormat::Unknown,
|
||||
};
|
||||
|
||||
values.push(Value::FormatOverride(index as i32, format));
|
||||
}
|
||||
|
||||
|
||||
if let Some(modulo) = program.named("modulo").next() {
|
||||
let modulo =
|
||||
u32::from_str(modulo.value()).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: index,
|
||||
row: 0,
|
||||
col: 0,
|
||||
kind: ParseErrorKind::UnsignedInt,
|
||||
})?;
|
||||
values.push(Value::FrameCountMod(index as i32, modulo))
|
||||
}
|
||||
|
||||
|
||||
|
||||
if let Some(vertex) = program.named("vertex").next() {
|
||||
let mut path = root.as_ref().to_path_buf();
|
||||
path.push(vertex.value());
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
values.push(Value::Shader(
|
||||
index as i32,
|
||||
ShaderType::Quark(ShaderStage::Vertex),
|
||||
path,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
if let Some(fragment) = program.named("fragment").next() {
|
||||
let mut path = root.as_ref().to_path_buf();
|
||||
path.push(fragment.value());
|
||||
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
values.push(Value::Shader(
|
||||
index as i32,
|
||||
ShaderType::Quark(ShaderStage::Fragment),
|
||||
path,
|
||||
))
|
||||
}
|
||||
|
||||
for (index, texture) in program.named("pixmap").enumerate() {
|
||||
let mut path = root.as_ref().to_path_buf();
|
||||
path.push(texture.value());
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: index.to_string(),
|
||||
filter_mode: texture.named("filter")
|
||||
.next()
|
||||
.map(|filter| FilterMode::from_str(filter.value().trim()).unwrap())
|
||||
.unwrap_or_default(),
|
||||
wrap_mode: texture.named("wrap")
|
||||
.next()
|
||||
.map(|wrap| WrapMode::from_str(wrap.value().trim()).unwrap())
|
||||
.unwrap_or_default(),
|
||||
mipmap: false,
|
||||
path,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_shader() {
|
||||
let preset = parse_bml_node("../test/quark-shaders/CRT-Royale.shader").unwrap();
|
||||
let values = parse_values(&preset, "../test/quark-shaders/CRT-Royale.shader");
|
||||
eprintln!("{values:#?}");
|
||||
|
||||
}
|
||||
}
|
8
librashader-presets/src/slang/mod.rs
Normal file
8
librashader-presets/src/slang/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod parse;
|
||||
mod token;
|
||||
|
||||
pub(crate) type Span<'a> = LocatedSpan<&'a str>;
|
||||
|
||||
use nom_locate::LocatedSpan;
|
||||
pub use parse::parse_preset;
|
||||
pub use parse::parse_values;
|
554
librashader-presets/src/slang/parse.rs
Normal file
554
librashader-presets/src/slang/parse.rs
Normal file
|
@ -0,0 +1,554 @@
|
|||
use crate::parse::{ShaderType, Value};
|
||||
use crate::slang::token::{do_lex, Token};
|
||||
use crate::slang::Span;
|
||||
use crate::{remove_if, ParseErrorKind, ParsePresetError, ScaleFactor, ScaleType};
|
||||
use librashader_common::{FilterMode, WrapMode};
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::digit1;
|
||||
use nom::combinator::{eof, map_res};
|
||||
use nom::IResult;
|
||||
use num_traits::ToPrimitive;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn from_int(input: Span) -> Result<i32, ParsePresetError> {
|
||||
// Presets like to commit ✨CRIMES✨ and end their lines with a ";".
|
||||
// It's too hard to put this in the lexer because we can't tell between
|
||||
// semicolon crimes or a valid param/texture name listing.
|
||||
let to_parse = input.trim().trim_end_matches(";");
|
||||
i32::from_str(to_parse)
|
||||
.map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Int,
|
||||
})
|
||||
.or_else(|e| {
|
||||
// An even more egregious ✨CRIME✨ is using a float as a shader index.
|
||||
let result = f32::from_str(to_parse).map_err(|_| e)?;
|
||||
let result = result
|
||||
.trunc()
|
||||
.to_i32()
|
||||
.ok_or(ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Int,
|
||||
})?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_ul(input: Span) -> Result<u32, ParsePresetError> {
|
||||
// Presets like to commit ✨CRIMES✨ and end their lines with a ";".
|
||||
// It's too hard to put this in the lexer because we can't tell between
|
||||
// semicolon crimes or a valid param/texture name listing.
|
||||
u32::from_str(input.trim().trim_end_matches(";")).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::UnsignedInt,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_float(input: Span) -> Result<f32, ParsePresetError> {
|
||||
// Presets like to commit ✨CRIMES✨ and end their lines with a ";".
|
||||
// It's too hard to put this in the lexer because we can't tell between
|
||||
// semicolon crimes or a valid param/texture name listing.
|
||||
f32::from_str(input.trim().trim_end_matches(";")).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Float,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_bool(input: Span) -> Result<bool, ParsePresetError> {
|
||||
if let Ok(i) = i32::from_str(input.trim()) {
|
||||
return match i {
|
||||
1 => Ok(true),
|
||||
0 => Ok(false),
|
||||
_ => Err(ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Bool,
|
||||
}),
|
||||
};
|
||||
}
|
||||
bool::from_str(input.trim()).map_err(|_| ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Bool,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_indexed_key<'a>(key: &'static str, input: Span<'a>) -> IResult<Span<'a>, i32> {
|
||||
let (input, _) = tag(key)(input)?;
|
||||
let (input, idx) = map_res(digit1, from_int)(input)?;
|
||||
let (input, _) = eof(input)?;
|
||||
Ok((input, idx))
|
||||
}
|
||||
|
||||
pub const SHADER_MAX_REFERENCE_DEPTH: usize = 16;
|
||||
|
||||
fn load_child_reference_strings(
|
||||
root_references: Vec<PathBuf>,
|
||||
root_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<(PathBuf, String)>, ParsePresetError> {
|
||||
let root_path = root_path.as_ref();
|
||||
|
||||
let mut reference_depth = 0;
|
||||
let mut reference_strings: VecDeque<(PathBuf, String)> = VecDeque::new();
|
||||
let root_references = vec![(root_path.to_path_buf(), root_references)];
|
||||
let mut root_references = VecDeque::from(root_references);
|
||||
// search needs to be depth first to allow for overrides.
|
||||
while let Some((reference_root, referenced_paths)) = root_references.pop_front() {
|
||||
if reference_depth > SHADER_MAX_REFERENCE_DEPTH {
|
||||
return Err(ParsePresetError::ExceededReferenceDepth);
|
||||
}
|
||||
// enter the current root
|
||||
reference_depth += 1;
|
||||
// canonicalize current root
|
||||
let reference_root = reference_root
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(reference_root.to_path_buf(), e))?;
|
||||
|
||||
// resolve all referenced paths against root
|
||||
// println!("Resolving {referenced_paths:?} against {reference_root:?}.");
|
||||
|
||||
for path in referenced_paths {
|
||||
let mut path = reference_root
|
||||
.join(path.clone())
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
|
||||
// println!("Opening {:?}", path);
|
||||
let mut reference_contents = String::new();
|
||||
File::open(&path)
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?
|
||||
.read_to_string(&mut reference_contents)
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
|
||||
|
||||
let mut new_tokens = do_lex(&reference_contents)?;
|
||||
let new_references: Vec<PathBuf> = new_tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
path.pop();
|
||||
reference_strings.push_front((path.clone(), reference_contents));
|
||||
if !new_references.is_empty() {
|
||||
root_references.push_front((path, new_references));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(reference_strings.into())
|
||||
}
|
||||
|
||||
pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let path = path.as_ref();
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
let mut contents = String::new();
|
||||
File::open(&path)
|
||||
.and_then(|mut f| f.read_to_string(&mut contents))
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
let tokens = do_lex(&contents)?;
|
||||
parse_values(tokens, path)
|
||||
}
|
||||
|
||||
pub fn parse_values(
|
||||
mut tokens: Vec<Token>,
|
||||
root_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let mut root_path = root_path.as_ref().to_path_buf();
|
||||
if root_path.is_relative() {
|
||||
return Err(ParsePresetError::RootPathWasNotAbsolute);
|
||||
}
|
||||
if !root_path.is_dir() {
|
||||
// we don't really care if this doesn't do anything because a non-canonical root path will
|
||||
// fail at a later stage during resolution.
|
||||
root_path.pop();
|
||||
}
|
||||
|
||||
let references: Vec<PathBuf> = tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
// unfortunately we need to lex twice because there's no way to know the references ahead of time.
|
||||
let child_strings = load_child_reference_strings(references, &root_path)?;
|
||||
let mut all_tokens: Vec<(&Path, Vec<Token>)> = Vec::new();
|
||||
|
||||
for (path, string) in child_strings.iter() {
|
||||
// lex the child tokens
|
||||
let mut tokens = do_lex(string.as_ref())?;
|
||||
tokens.retain(|token| *token.key.fragment() != "#reference");
|
||||
all_tokens.push((path.as_path(), tokens))
|
||||
}
|
||||
|
||||
// load depth first, so all child tokens are first.
|
||||
// Later tokens take precedence.
|
||||
all_tokens.push((root_path.as_path(), tokens));
|
||||
// collect all possible parameter names.
|
||||
let mut parameter_names: Vec<&str> = Vec::new();
|
||||
for (_, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| *token.key.fragment() == "parameters") {
|
||||
let parameter_name_string: &str = token.value.fragment();
|
||||
for parameter_name in parameter_name_string.split(';') {
|
||||
parameter_names.push(parameter_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect all possible texture names.
|
||||
let mut texture_names: Vec<&str> = Vec::new();
|
||||
for (_, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| *token.key.fragment() == "textures") {
|
||||
let texture_name_string: &str = token.value.fragment();
|
||||
for texture_name in texture_name_string.split(';') {
|
||||
texture_names.push(texture_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut values = Vec::new();
|
||||
// resolve shader paths.
|
||||
for (path, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| parse_indexed_key("shader", token.key).is_ok()) {
|
||||
let (_, index) = parse_indexed_key("shader", token.key).map_err(|e| match e {
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => {
|
||||
let input: Span = e.input;
|
||||
ParsePresetError::ParserError {
|
||||
offset: input.location_offset(),
|
||||
row: input.location_line(),
|
||||
col: input.get_column(),
|
||||
kind: ParseErrorKind::Index("shader"),
|
||||
}
|
||||
}
|
||||
_ => ParsePresetError::ParserError {
|
||||
offset: 0,
|
||||
row: 0,
|
||||
col: 0,
|
||||
kind: ParseErrorKind::Index("shader"),
|
||||
},
|
||||
})?;
|
||||
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(relative_path.clone(), e))?;
|
||||
values.push(Value::Shader(index, ShaderType::Slang, relative_path))
|
||||
}
|
||||
}
|
||||
|
||||
// resolve texture paths
|
||||
let mut textures = Vec::new();
|
||||
for (path, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| texture_names.contains(token.key.fragment())) {
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(relative_path.clone(), e))?;
|
||||
textures.push((token.key, relative_path))
|
||||
}
|
||||
}
|
||||
|
||||
let mut tokens: Vec<(&Path, Token)> = all_tokens
|
||||
.into_iter()
|
||||
.flat_map(|(p, token)| token.into_iter().map(move |t| (p, t)))
|
||||
.collect();
|
||||
|
||||
for (texture, path) in textures {
|
||||
let mipmap = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_mipmap")
|
||||
&& t.key.len() == texture.len() + "_mipmap".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let linear = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_linear")
|
||||
&& t.key.len() == texture.len() + "_linear".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let wrap_mode = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& (t.key.ends_with("_wrap_mode") || t.key.ends_with("_repeat_mode"))
|
||||
&& (t.key.len() == texture.len() + "_wrap_mode".len()
|
||||
|| t.key.len() == texture.len() + "_repeat_mode".len())
|
||||
})
|
||||
// NOPANIC: infallible
|
||||
.map_or_else(WrapMode::default, |(_, v)| {
|
||||
WrapMode::from_str(&v.value).unwrap()
|
||||
});
|
||||
|
||||
// This really isn't supported but crt-torridgristle uses this syntax.
|
||||
// Again, don't know how this works in RA but RA's parser isn't as strict as ours.
|
||||
let filter = remove_if(&mut tokens, |(_, t)| {
|
||||
t.key.starts_with("filter_")
|
||||
&& t.key.ends_with(*texture)
|
||||
&& t.key.len() == "filter_".len() + texture.len()
|
||||
})
|
||||
// NOPANIC: infallible
|
||||
.map_or(None, |(_, v)| Some(FilterMode::from_str(&v.value).unwrap()));
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: texture.to_string(),
|
||||
filter_mode: filter.unwrap_or(if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
FilterMode::Nearest
|
||||
}),
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
let mut rest_tokens = Vec::new();
|
||||
// hopefully no more textures left in the token tree
|
||||
for (p, token) in tokens {
|
||||
if parameter_names.contains(token.key.fragment()) {
|
||||
let param_val = from_float(token.value)
|
||||
// This is literally just to work around BEAM_PROFILE in crt-hyllian-sinc-glow.slangp
|
||||
// which has ""0'.000000". This somehow works in RA because it defaults to 0, probably.
|
||||
// This hack is only used for **known** parameter names. If we tried this for undeclared
|
||||
// params (god help me), it would be pretty bad because we lose texture path fallback.
|
||||
.unwrap_or(0.0);
|
||||
values.push(Value::Parameter(
|
||||
token.key.fragment().to_string(),
|
||||
param_val,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
if token.key.fragment() == &"shaders" {
|
||||
let shader_count = from_int(token.value)?;
|
||||
values.push(Value::ShaderCount(shader_count));
|
||||
continue;
|
||||
}
|
||||
if token.key.fragment() == &"feedback_pass" {
|
||||
let feedback_pass = from_int(token.value)?;
|
||||
values.push(Value::FeedbackPass(feedback_pass));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("filter_linear", token.key) {
|
||||
let linear = from_bool(token.value)?;
|
||||
values.push(Value::FilterMode(
|
||||
idx,
|
||||
if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
FilterMode::Nearest
|
||||
},
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("wrap_mode", token.key) {
|
||||
let wrap_mode = WrapMode::from_str(&token.value).unwrap();
|
||||
values.push(Value::WrapMode(idx, wrap_mode));
|
||||
continue;
|
||||
}
|
||||
|
||||
// crt-geom uses repeat_mode...
|
||||
if let Ok((_, idx)) = parse_indexed_key("repeat_mode", token.key) {
|
||||
let wrap_mode = WrapMode::from_str(&token.value).unwrap();
|
||||
values.push(Value::WrapMode(idx, wrap_mode));
|
||||
continue;
|
||||
}
|
||||
|
||||
// crt-royale uses 'texture_wrap_mode' instead of 'wrap_mode', I have no idea
|
||||
// how this possibly could work in RA, but here it is..
|
||||
if let Ok((_, idx)) = parse_indexed_key("texture_wrap_mode", token.key) {
|
||||
let wrap_mode = WrapMode::from_str(&token.value).unwrap();
|
||||
values.push(Value::WrapMode(idx, wrap_mode));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("frame_count_mod", token.key) {
|
||||
let frame_count_mod = from_ul(token.value)?;
|
||||
values.push(Value::FrameCountMod(idx, frame_count_mod));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("srgb_framebuffer", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::SrgbFramebuffer(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("float_framebuffer", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::FloatFramebuffer(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("mipmap_input", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::MipmapInput(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
|
||||
// vector-glow-alt-render.slangp uses "mipmap" for pass 1, but "mipmap_input" for everything else.
|
||||
if let Ok((_, idx)) = parse_indexed_key("mipmap", token.key) {
|
||||
let enabled = from_bool(token.value)?;
|
||||
values.push(Value::MipmapInput(idx, enabled));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok((_, idx)) = parse_indexed_key("alias", token.key) {
|
||||
values.push(Value::Alias(idx, token.value.to_string()));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type", token.key) {
|
||||
let scale_type = ScaleType::from_str(token.value.trim())?;
|
||||
values.push(Value::ScaleType(idx, scale_type));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type_x", token.key) {
|
||||
let scale_type = ScaleType::from_str(token.value.trim())?;
|
||||
values.push(Value::ScaleTypeX(idx, scale_type));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_type_y", token.key) {
|
||||
let scale_type = ScaleType::from_str(token.value.trim())?;
|
||||
values.push(Value::ScaleTypeY(idx, scale_type));
|
||||
continue;
|
||||
}
|
||||
rest_tokens.push((p, token))
|
||||
}
|
||||
|
||||
let mut undeclared_textures = Vec::new();
|
||||
for (path, token) in &rest_tokens {
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale", token.key) {
|
||||
let scale = if values.iter().any(|t| matches!(*t, Value::ScaleType(match_idx, ScaleType::Absolute) if match_idx == idx)) {
|
||||
let scale = from_int(token.value)?;
|
||||
ScaleFactor::Absolute(scale)
|
||||
} else {
|
||||
let scale = from_float(token.value)?;
|
||||
ScaleFactor::Float(scale)
|
||||
};
|
||||
|
||||
values.push(Value::Scale(idx, scale));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_x", token.key) {
|
||||
let scale = if values.iter().any(|t| matches!(*t, Value::ScaleType(match_idx, ScaleType::Absolute) | Value::ScaleTypeX(match_idx, ScaleType::Absolute) if match_idx == idx)) {
|
||||
let scale = from_int(token.value)?;
|
||||
ScaleFactor::Absolute(scale)
|
||||
} else {
|
||||
let scale = from_float(token.value)?;
|
||||
ScaleFactor::Float(scale)
|
||||
};
|
||||
|
||||
values.push(Value::ScaleX(idx, scale));
|
||||
continue;
|
||||
}
|
||||
if let Ok((_, idx)) = parse_indexed_key("scale_y", token.key) {
|
||||
let scale = if values.iter().any(|t| matches!(*t, Value::ScaleType(match_idx, ScaleType::Absolute) | Value::ScaleTypeY(match_idx, ScaleType::Absolute) if match_idx == idx)) {
|
||||
let scale = from_int(token.value)?;
|
||||
ScaleFactor::Absolute(scale)
|
||||
} else {
|
||||
let scale = from_float(token.value)?;
|
||||
ScaleFactor::Float(scale)
|
||||
};
|
||||
|
||||
values.push(Value::ScaleY(idx, scale));
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle undeclared parameters after parsing everything else as a last resort.
|
||||
if let Ok(param_val) = from_float(token.value) {
|
||||
values.push(Value::Parameter(
|
||||
token.key.fragment().to_string(),
|
||||
param_val,
|
||||
));
|
||||
}
|
||||
// very last resort, assume undeclared texture (must have extension)
|
||||
else if Path::new(token.value.fragment()).extension().is_some()
|
||||
&& ["_mipmap", "_linear", "_wrap_mode", "_repeat_mode"]
|
||||
.iter()
|
||||
.all(|k| !token.key.ends_with(k))
|
||||
{
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(relative_path.clone(), e))?;
|
||||
undeclared_textures.push((token.key, relative_path));
|
||||
}
|
||||
|
||||
// we tried our best
|
||||
}
|
||||
|
||||
// Since there are undeclared textures we need to deal with potential mipmap information.
|
||||
for (texture, path) in undeclared_textures {
|
||||
let mipmap = remove_if(&mut rest_tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_mipmap")
|
||||
&& t.key.len() == texture.len() + "_mipmap".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let linear = remove_if(&mut rest_tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& t.key.ends_with("_linear")
|
||||
&& t.key.len() == texture.len() + "_linear".len()
|
||||
})
|
||||
.map_or_else(|| Ok(false), |(_, v)| from_bool(v.value))?;
|
||||
|
||||
let wrap_mode = remove_if(&mut rest_tokens, |(_, t)| {
|
||||
t.key.starts_with(*texture)
|
||||
&& (t.key.ends_with("_wrap_mode") || t.key.ends_with("_repeat_mode"))
|
||||
&& (t.key.len() == texture.len() + "_wrap_mode".len()
|
||||
|| t.key.len() == texture.len() + "_repeat_mode".len())
|
||||
})
|
||||
// NOPANIC: infallible
|
||||
.map_or_else(WrapMode::default, |(_, v)| {
|
||||
WrapMode::from_str(&v.value).unwrap()
|
||||
});
|
||||
|
||||
values.push(Value::Texture {
|
||||
name: texture.to_string(),
|
||||
filter_mode: if linear {
|
||||
FilterMode::Linear
|
||||
} else {
|
||||
FilterMode::Nearest
|
||||
},
|
||||
wrap_mode,
|
||||
mipmap,
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
// all tokens should be ok to process now.
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::slang::parse_preset;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
pub fn parse_basic() {
|
||||
let root =
|
||||
PathBuf::from("../test/slang-shaders/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
|
||||
let basic = parse_preset(root);
|
||||
eprintln!("{basic:?}");
|
||||
assert!(basic.is_ok());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use crate::error::ParsePresetError;
|
||||
use crate::parse::Span;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{is_not, take_until};
|
||||
use nom::character::complete::{char, line_ending, multispace1, not_line_ending};
|
||||
|
@ -7,6 +6,7 @@ use nom::character::complete::{char, line_ending, multispace1, not_line_ending};
|
|||
use nom::combinator::{eof, map_res, value};
|
||||
use nom::error::{ErrorKind, ParseError};
|
||||
|
||||
use crate::slang::Span;
|
||||
use nom::sequence::delimited;
|
||||
use nom::{
|
||||
bytes::complete::tag, character::complete::multispace0, IResult, InputIter, InputLength,
|
||||
|
@ -155,7 +155,7 @@ pub fn do_lex(input: &str) -> Result<Vec<Token>, ParsePresetError> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parse::token::{do_lex, single_comment};
|
||||
use crate::slang::token::{do_lex, single_comment};
|
||||
|
||||
#[test]
|
||||
fn parses_single_line_comment() {
|
|
@ -6,7 +6,7 @@ use crate::reflect::semantics::{
|
|||
Semantic, ShaderSemantics, TextureSemantics, UniformSemantic, UniqueSemantics,
|
||||
};
|
||||
use librashader_preprocess::{PreprocessError, ShaderSource};
|
||||
use librashader_presets::{ShaderPassConfig, TextureConfig};
|
||||
use librashader_presets::{ShaderPassConfig, ShaderPath, TextureConfig};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// Artifacts of a reflected and compiled shader pass.
|
||||
|
@ -83,7 +83,12 @@ where
|
|||
let passes = passes
|
||||
.into_iter()
|
||||
.map(|shader| {
|
||||
let source: ShaderSource = ShaderSource::load(&shader.name)?;
|
||||
let source = match &shader.source_path {
|
||||
ShaderPath::Slang(source_path) => ShaderSource::load(source_path)?,
|
||||
ShaderPath::Quark { vertex, fragment } => {
|
||||
panic!("quark shaders not implemented")
|
||||
}
|
||||
};
|
||||
|
||||
let compiled = C::compile(&source)?;
|
||||
let reflect = T::from_compilation(compiled)?;
|
||||
|
|
|
@ -12,14 +12,13 @@ use librashader_runtime_d3d11::options::FilterChainOptionsD3D11;
|
|||
// const FILTER_PATH: &str =
|
||||
// "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID].slangp";
|
||||
|
||||
const FILTER_PATH: &str =
|
||||
"../test/shaders_slang/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
|
||||
const FILTER_PATH: &str = "../test/shaders_slang/scalefx/scalefx-9x.slangp";
|
||||
|
||||
// const FILTER_PATH: &str = "../test/slang-shaders/test/history.slangp";
|
||||
// const FILTER_PATH: &str = "../test/slang-shaders/test/feedback.slangp";
|
||||
|
||||
// const FILTER_PATH: &str = "../test/slang-shaders/crt/crt-royale.slangp";
|
||||
const IMAGE_PATH: &str = "../triangle.png";
|
||||
const IMAGE_PATH: &str = "../test/finalfightlong.png";
|
||||
#[test]
|
||||
fn triangle_d3d11_args() {
|
||||
let mut args = std::env::args();
|
||||
|
|
|
@ -432,7 +432,7 @@ impl FilterChainD3D12 {
|
|||
librashader_reflect::back::dxil::ShaderModel::ShaderModel6_0,
|
||||
))?;
|
||||
|
||||
let render_format = if let Some(format) = config.get_format_override() {
|
||||
let render_format = if let Some(format) = config.framebuffer_format_override {
|
||||
format
|
||||
} else if source.format != ImageFormat::Unknown {
|
||||
source.format
|
||||
|
|
|
@ -428,7 +428,7 @@ impl FilterChainVulkan {
|
|||
|
||||
let render_pass_format = if !use_render_pass {
|
||||
vk::Format::UNDEFINED
|
||||
} else if let Some(format) = config.get_format_override() {
|
||||
} else if let Some(format) = config.framebuffer_format_override {
|
||||
format.into()
|
||||
} else if source.format != ImageFormat::Unknown {
|
||||
source.format.into()
|
||||
|
|
|
@ -13,7 +13,7 @@ pub trait FilterPassMeta {
|
|||
#[inline(always)]
|
||||
fn get_format(&self) -> ImageFormat {
|
||||
let fb_format = self.framebuffer_format();
|
||||
if let Some(format) = self.config().get_format_override() {
|
||||
if let Some(format) = self.config().framebuffer_format_override {
|
||||
format
|
||||
} else if fb_format == ImageFormat::Unknown {
|
||||
ImageFormat::R8G8B8A8Unorm
|
||||
|
|
|
@ -61,7 +61,9 @@ pub mod presets {
|
|||
let iters: Result<Vec<Vec<ShaderParameter>>, PreprocessError> = preset
|
||||
.shaders
|
||||
.iter()
|
||||
.map(|s| ShaderSource::load(&s.name).map(|s| s.parameters.into_values().collect()))
|
||||
.map(|s| {
|
||||
ShaderSource::load(&s.source_path).map(|s| s.parameters.into_values().collect())
|
||||
})
|
||||
.collect();
|
||||
let iters = iters?;
|
||||
Ok(iters.into_iter().flatten())
|
||||
|
|
1
test/quark-shaders
Submodule
1
test/quark-shaders
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 2fa0ba174042f2b5f0a9d3408adba75e0b03a8ff
|
|
@ -1 +1 @@
|
|||
Subproject commit a6e11453ad8c62931c62eeb79d51c70887b40bba
|
||||
Subproject commit e04aa575febae5927db6b445a3c47b8f4221df78
|
Loading…
Reference in a new issue