use std::{collections::BTreeMap, path::Path};
use ordered_float::OrderedFloat;
use smol_str::SmolStr;
use strum::{Display, FromRepr};
use crate::error::{LoadShaderDatabaseError, SaveShaderDatabaseError};
mod io;
type IndexMap<K, V> = indexmap::IndexMap<K, V, ahash::RandomState>;
#[derive(Debug, PartialEq, Clone)]
pub struct ShaderDatabase(io::ShaderDatabaseIndexed);
impl ShaderDatabase {
#[tracing::instrument(skip_all)]
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, LoadShaderDatabaseError> {
let indexed = io::ShaderDatabaseIndexed::from_file(path)?;
Ok(Self(indexed))
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), SaveShaderDatabaseError> {
self.0.save(path)?;
Ok(())
}
pub fn shader_program(&self, hash: ProgramHash) -> Option<ShaderProgram> {
self.0.shader_program(hash)
}
pub fn from_programs(programs: BTreeMap<ProgramHash, ShaderProgram>) -> Self {
Self(io::ShaderDatabaseIndexed::from_programs(programs))
}
pub fn merge(self, others: impl Iterator<Item = Self>) -> Self {
Self(self.0.merge(others.into_iter().map(|o| o.0)))
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ProgramHash(u32);
impl ProgramHash {
pub fn from_mths(mths: &xc3_lib::mths::Mths) -> Self {
let mut hasher = crc32fast::Hasher::new();
hasher.update(&mths.data);
Self(hasher.finalize())
}
pub fn from_spch_program(
program: &xc3_lib::spch::ShaderProgram,
vertex: &Option<xc3_lib::spch::ShaderBinary>,
fragment: &Option<xc3_lib::spch::ShaderBinary>,
) -> Self {
let mut hasher = crc32fast::Hasher::new();
hasher.update(&program.program_data);
if let Some(fragment) = fragment {
hasher.update(&fragment.program_binary);
}
if let Some(vertex) = vertex {
hasher.update(&vertex.program_binary);
}
Self(hasher.finalize())
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct ShaderProgram {
pub output_dependencies: IndexMap<SmolStr, usize>,
pub outline_width: Option<Dependency>,
pub normal_intensity: Option<usize>,
pub exprs: Vec<OutputExpr>,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Dependency {
Int(i32),
Float(OrderedFloat<f32>),
Buffer(BufferDependency),
Texture(TextureDependency),
Attribute(AttributeDependency),
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct BufferDependency {
pub name: SmolStr,
pub field: SmolStr,
pub index: Option<usize>,
pub channel: Option<char>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct TextureDependency {
pub name: SmolStr,
pub channel: Option<char>,
pub texcoords: Vec<usize>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct AttributeDependency {
pub name: SmolStr,
pub channel: Option<char>,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Display, FromRepr)]
pub enum Operation {
Unk,
Mix,
Mul,
Div,
Add,
Sub,
Fma,
MulRatio,
AddNormalX,
AddNormalY,
Overlay,
Overlay2,
OverlayRatio,
Power,
Min,
Max,
Clamp,
Abs,
Fresnel,
Sqrt,
TexMatrix,
TexParallaxX,
TexParallaxY,
ReflectX,
ReflectY,
ReflectZ,
Floor,
Select,
Equal,
NotEqual,
Less,
Greater,
LessEqual,
GreaterEqual,
Dot4,
NormalMapX,
NormalMapY,
NormalMapZ,
MonochromeX,
MonochromeY,
MonochromeZ,
Negate,
FurInstanceAlpha,
Float,
Int,
Uint,
Truncate,
FloatBitsToInt,
IntBitsToFloat,
UintBitsToFloat,
InverseSqrt,
Not,
LeftShift,
RightShift,
PartialDerivativeX,
PartialDerivativeY,
Exp2,
Log2,
Sin,
Cos,
}
impl Default for Operation {
fn default() -> Self {
Self::Unk
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum OutputExpr {
Value(Dependency),
Func {
op: Operation,
args: Vec<usize>,
},
}
impl Default for OutputExpr {
fn default() -> Self {
Self::Func {
op: Operation::Unk,
args: Vec::new(),
}
}
}
impl std::fmt::Display for OutputExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputExpr::Value(d) => write!(f, "{d}"),
OutputExpr::Func { op, args } => {
let args: Vec<_> = args.iter().map(|a| format!("var{a}")).collect();
write!(f, "{op}({})", args.join(", "))
}
}
}
}
impl std::fmt::Display for AttributeDependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", &self.name, channels(self.channel))
}
}
impl std::fmt::Display for BufferDependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}{}",
&self.name,
if self.field.is_empty() {
String::new()
} else {
format!(".{}", &self.field)
},
self.index.map(|i| format!("[{i}]")).unwrap_or_default(),
channels(self.channel)
)
}
}
impl std::fmt::Display for TextureDependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let args: Vec<_> = self.texcoords.iter().map(|t| format!("var{t}")).collect();
write!(
f,
"Texture({}, {}){}",
&self.name,
args.join(", "),
channels(self.channel)
)
}
}
impl std::fmt::Display for Dependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Dependency::Int(i) => write!(f, "{i:?}"),
Dependency::Float(c) => write!(f, "{c:?}"),
Dependency::Buffer(b) => write!(f, "{b}"),
Dependency::Texture(t) => write!(f, "{t}"),
Dependency::Attribute(a) => write!(f, "{a}"),
}
}
}
fn channels(c: Option<char>) -> String {
c.map(|c| format!(".{c}")).unwrap_or_default()
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for AttributeDependency {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
Ok(Self {
name: crate::arbitrary_smolstr(u)?,
channel: u.arbitrary()?,
})
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for BufferDependency {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
Ok(Self {
name: crate::arbitrary_smolstr(u)?,
field: crate::arbitrary_smolstr(u)?,
index: u.arbitrary()?,
channel: u.arbitrary()?,
})
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for TextureDependency {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
Ok(Self {
name: crate::arbitrary_smolstr(u)?,
channel: u.arbitrary()?,
texcoords: u.arbitrary()?,
})
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for ShaderProgram {
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
let output_dependencies: Vec<(String, usize)> = u.arbitrary()?;
Ok(Self {
output_dependencies: output_dependencies
.into_iter()
.map(|(k, v)| (k.into(), v))
.collect(),
outline_width: u.arbitrary()?,
normal_intensity: u.arbitrary()?,
exprs: u.arbitrary()?,
})
}
}