use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
use super::shader_manager::{LocationAllocator, get_env};
use minijinja::value::Value;
use myth_resources::shader_defines::ShaderDefines;
use serde::Serialize;
#[derive(Debug, Clone, Default)]
pub struct ShaderCompilationOptions {
pub(crate) defines: ShaderDefines,
pub(crate) code_blocks: BTreeMap<String, String>,
}
impl ShaderCompilationOptions {
#[must_use]
pub fn new() -> Self {
Self {
defines: ShaderDefines::new(),
code_blocks: BTreeMap::new(),
}
}
#[must_use]
pub fn from_merged(
mat_defines: &ShaderDefines,
geo_defines: &ShaderDefines,
scene_defines: &ShaderDefines,
item_defines: &ShaderDefines,
) -> Self {
let mut defines = mat_defines.clone();
defines.merge(geo_defines);
defines.merge(scene_defines);
defines.merge(item_defines);
Self {
defines,
code_blocks: BTreeMap::new(),
}
}
#[inline]
#[must_use]
pub fn defines(&self) -> &ShaderDefines {
&self.defines
}
#[inline]
pub fn defines_mut(&mut self) -> &mut ShaderDefines {
&mut self.defines
}
pub fn add_define(&mut self, key: &str, value: &str) {
self.defines.set(key, value);
}
pub fn inject_code(&mut self, key: impl Into<String>, code: impl Into<String>) {
self.code_blocks.insert(key.into(), code.into());
}
#[must_use]
pub fn compute_hash(&self) -> u64 {
let mut h = rustc_hash::FxHasher::default();
Hash::hash(self, &mut h);
h.finish()
}
fn to_template_map(&self) -> BTreeMap<String, String> {
self.defines
.iter_strings()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
}
impl Hash for ShaderCompilationOptions {
fn hash<H: Hasher>(&self, state: &mut H) {
self.defines.as_slice().hash(state);
self.code_blocks.hash(state);
}
}
impl PartialEq for ShaderCompilationOptions {
fn eq(&self, other: &Self) -> bool {
self.defines == other.defines && self.code_blocks == other.code_blocks
}
}
impl Eq for ShaderCompilationOptions {}
#[derive(Serialize)]
struct ShaderContext<'a> {
#[serde(flatten)]
defines: BTreeMap<String, String>,
#[serde(flatten)]
code_blocks: &'a BTreeMap<String, String>,
loc: Value,
}
pub struct ShaderGenerator;
impl ShaderGenerator {
fn wrap_material_body(shader_body: &str) -> String {
format!(
"{{{{ vertex_input_code }}}}\n{{{{ binding_code }}}}\n{{{{ scene_lighting_structs }}}}\n$$ if USE_CLUSTERED_SHADING is defined\n{{{{ clustered_lighting_structs }}}}\n$$ endif\n{{$ include 'core/vertex_output' $}}\n{{$ include 'core/fragment_output' $}}\n\n{shader_body}",
)
}
fn build_context(options: &ShaderCompilationOptions) -> ShaderContext<'_> {
let allocator = LocationAllocator::new();
let mut defines = options.to_template_map();
if cfg!(feature = "advanced_noise") {
defines.insert("HIGH_END_NOISE".to_string(), "1".to_string());
}
ShaderContext {
defines,
code_blocks: &options.code_blocks,
loc: Value::from_object(allocator),
}
}
#[must_use]
pub fn generate_shader(template_name: &str, options: &ShaderCompilationOptions) -> String {
let env = get_env();
let ctx = Self::build_context(options);
let template = env
.get_template(template_name)
.expect("Shader template not found");
let source = template.render(&ctx).expect("Shader render failed");
format!("// === Auto-generated Unified Shader ===\n{source}")
}
#[must_use]
pub fn generate_custom_shader(
template_name: &str,
template_source: &str,
options: &ShaderCompilationOptions,
) -> String {
let env = get_env();
let ctx = Self::build_context(options);
let source = env
.render_named_str(template_name, template_source, &ctx)
.expect("Custom shader render failed");
format!("// === Auto-generated Unified Shader ===\n{source}")
}
#[must_use]
pub fn generate_material_shader(
template_name: &str,
shader_body: &str,
options: &ShaderCompilationOptions,
) -> String {
let env = get_env();
let ctx = Self::build_context(options);
let template_source = Self::wrap_material_body(shader_body);
let source = env
.render_named_str(template_name, &template_source, &ctx)
.expect("Custom shader render failed");
format!("// === Auto-generated Unified Shader ===\n{source}")
}
}