use std::{
collections::HashSet,
path::{Path, PathBuf},
};
use crate::{
include::IncludeHandler,
position::{ShaderFileRange, ShaderRange},
shader::ShaderContextParams,
shader_error::ShaderDiagnostic,
symbols::{
shader_module::ShaderSymbols,
symbol_list::{ShaderSymbolList, ShaderSymbolListRef},
symbols::{
ShaderSymbol, ShaderSymbolData, ShaderSymbolMode, ShaderSymbolRuntime,
ShaderSymbolRuntimeContext,
},
},
};
#[derive(Debug, Default, Clone)]
pub struct ShaderRegion {
pub range: ShaderRange,
pub is_active: bool, }
impl ShaderRegion {
pub fn new(range: ShaderRange, is_active: bool) -> Self {
Self { range, is_active }
}
}
#[derive(Debug, Default, Clone)]
pub struct ShaderPreprocessorContext {
defines: Vec<ShaderSymbol>,
include_handler: IncludeHandler,
dirty_files: HashSet<PathBuf>, depth: usize,
}
impl ShaderPreprocessorContext {
pub fn main(file_path: &Path, shader_params: ShaderContextParams) -> Self {
Self {
defines: shader_params
.defines
.iter()
.map(|(key, value)| ShaderSymbol {
label: key.clone(),
requirement: None,
data: ShaderSymbolData::Macro {
value: value.clone(),
parameters: vec![],
},
mode: ShaderSymbolMode::RuntimeContext(ShaderSymbolRuntimeContext::new()),
})
.collect(),
include_handler: IncludeHandler::main(
&file_path,
shader_params.includes,
shader_params.path_remapping,
),
dirty_files: HashSet::new(),
depth: 0,
}
}
pub fn mark_dirty(&mut self, file_path: PathBuf) {
self.dirty_files.insert(file_path);
}
pub fn search_path_in_includes(&mut self, path: &Path) -> Option<PathBuf> {
self.include_handler.search_path_in_includes(path)
}
pub fn push_directory_stack(&mut self, canonical_path: &Path) {
self.include_handler.push_directory_stack(canonical_path);
}
pub fn append_defines(&mut self, defines: Vec<ShaderPreprocessorDefine>) {
self.defines
.extend(defines.iter().map(|define| define.symbol.clone()));
}
pub fn increase_depth(&mut self) -> bool {
if self.depth < IncludeHandler::DEPTH_LIMIT {
self.depth += 1;
true
} else {
false
}
}
pub fn decrease_depth(&mut self) {
assert!(self.depth > 0, "Decreasing depth but zero.");
self.depth -= 1;
}
pub fn get_visited_count(&mut self, path: &Path) -> usize {
self.include_handler.get_visited_count(path)
}
pub fn is_dirty(&self, file_path: &Path, context: &ShaderPreprocessorContext) -> bool {
fn are_defines_equal(lhs: &Vec<ShaderSymbol>, rhs: &Vec<ShaderSymbol>) -> bool {
if lhs.len() != rhs.len() {
return false;
}
for lhs_symbol in lhs.iter() {
if rhs
.iter()
.find(|rhs_symbol| {
lhs_symbol.label == rhs_symbol.label
&& match (&lhs_symbol.data, &rhs_symbol.data) {
(
ShaderSymbolData::Macro {
value: l_value,
parameters: l_parameter,
},
ShaderSymbolData::Macro {
value: r_value,
parameters: r_parameter,
},
) => l_value == r_value || l_parameter == r_parameter,
_ => false,
}
})
.is_none()
{
return false;
}
}
true
}
fn are_includes_equal(lhs: &HashSet<PathBuf>, rhs: &HashSet<PathBuf>) -> bool {
if lhs.len() != rhs.len() {
return false;
}
for lhs_symbol in lhs.iter() {
if rhs
.iter()
.find(|rhs_symbol| lhs_symbol.as_os_str() == rhs_symbol.as_os_str())
.is_none()
{
return false;
}
}
true
}
!are_defines_equal(&context.defines, &self.defines)
|| !are_includes_equal(
context.include_handler.get_includes(),
self.include_handler.get_includes(),
)
|| context.dirty_files.contains(file_path)
}
pub fn get_define_value(&self, name: &str) -> Option<String> {
self.defines
.iter()
.find(|symbol| *symbol.label == *name)
.map(|symbol| match &symbol.data {
ShaderSymbolData::Macro {
value,
parameters: _,
} => value.clone(),
_ => panic!("Expected ShaderSymbolData::Macro"),
})
}
pub fn get_defines(&self) -> &Vec<ShaderSymbol> {
&self.defines
}
}
#[derive(Debug, Clone)]
pub struct ShaderPreprocessorInclude {
pub cache: Option<ShaderSymbols>,
symbol: ShaderSymbol,
}
#[derive(Debug, Clone)]
pub struct ShaderPreprocessorDefine {
symbol: ShaderSymbol,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum ShaderPreprocessorMode {
#[default]
Default,
Once,
OnceVisited,
}
#[derive(Debug, Default, Clone)]
pub struct ShaderPreprocessor {
pub context: ShaderPreprocessorContext,
pub includes: Vec<ShaderPreprocessorInclude>,
pub defines: Vec<ShaderPreprocessorDefine>,
pub regions: Vec<ShaderRegion>,
pub diagnostics: Vec<ShaderDiagnostic>, pub mode: ShaderPreprocessorMode,
}
impl ShaderPreprocessorDefine {
pub fn new(
name: String,
range: ShaderFileRange,
value: Option<String>,
arguments: Option<Vec<String>>,
) -> Self {
Self {
symbol: ShaderSymbol {
label: name.clone(),
requirement: None,
data: ShaderSymbolData::Macro {
value: match &value {
Some(value) => value.clone(),
None => "".into(),
},
parameters: arguments.unwrap_or_default(),
},
mode: ShaderSymbolMode::Runtime(ShaderSymbolRuntime::global(
range.file_path,
range.range,
)),
},
}
}
pub fn get_file_path(&self) -> &Path {
&self.symbol.mode.unwrap_runtime().file_path
}
pub fn get_range(&self) -> &ShaderRange {
&self.symbol.mode.unwrap_runtime().range
}
pub fn get_name(&self) -> &String {
&self.symbol.label
}
pub fn get_value(&self) -> Option<&String> {
match &self.symbol.data {
ShaderSymbolData::Macro {
value,
parameters: _,
} => Some(value),
_ => None,
}
}
pub fn get_parameters(&self) -> Option<&Vec<String>> {
match &self.symbol.data {
ShaderSymbolData::Macro {
value: _,
parameters,
} => Some(parameters),
_ => None,
}
}
}
impl ShaderPreprocessorInclude {
pub fn new(relative_path: String, absolute_path: PathBuf, range: ShaderFileRange) -> Self {
Self {
cache: None,
symbol: ShaderSymbol {
label: relative_path,
requirement: None,
data: ShaderSymbolData::Include {
target: absolute_path,
},
mode: ShaderSymbolMode::Runtime(ShaderSymbolRuntime::global(
range.file_path,
range.range,
)),
},
}
}
pub fn get_range(&self) -> &ShaderRange {
&self.symbol.mode.unwrap_runtime().range
}
pub fn get_file_range(&self) -> ShaderFileRange {
let runtime = self.symbol.mode.unwrap_runtime();
runtime.range.clone_into_file(runtime.file_path.clone())
}
pub fn get_relative_path(&self) -> &String {
&self.symbol.label
}
pub fn get_absolute_path(&self) -> &Path {
match &self.symbol.data {
ShaderSymbolData::Include { target } => &target,
_ => panic!("Expected ShaderSymbolData::Link"),
}
}
pub fn get_cache(&self) -> &ShaderSymbols {
self.cache.as_ref().unwrap()
}
pub fn get_cache_mut(&mut self) -> &mut ShaderSymbols {
self.cache.as_mut().unwrap()
}
}
impl ShaderPreprocessor {
pub fn new(context: ShaderPreprocessorContext) -> Self {
Self {
context: context,
includes: Vec::new(),
defines: Vec::new(),
regions: Vec::new(),
diagnostics: Vec::new(),
mode: ShaderPreprocessorMode::default(),
}
}
pub fn preprocess_symbols<'a>(
&'a self,
shader_symbols: &'a ShaderSymbolList,
) -> ShaderSymbolListRef<'a> {
let inactive_regions: Vec<&ShaderRegion> =
self.regions.iter().filter(|r| !r.is_active).collect();
let mut preprocessed_symbols =
shader_symbols.filter(move |_symbol_type, symbol| match &symbol.mode {
ShaderSymbolMode::Runtime(runtime) => inactive_regions
.iter()
.find(|r| r.range.contain_bounds(&runtime.range))
.is_none(),
ShaderSymbolMode::RuntimeContext(_) => true, ShaderSymbolMode::Intrinsic(_) => true, });
let mut define_symbols: Vec<&ShaderSymbol> =
self.defines.iter().map(|define| &define.symbol).collect();
let mut include_symbols: Vec<&ShaderSymbol> = self
.includes
.iter()
.map(|include| &include.symbol)
.collect();
preprocessed_symbols.macros.append(&mut define_symbols);
preprocessed_symbols.includes.append(&mut include_symbols);
preprocessed_symbols
}
}