mod glsl;
mod hlsl;
pub mod intrinsics;
pub mod prepocessor;
pub mod shader_module;
pub mod shader_module_parser;
pub mod symbol_list;
mod symbol_parser;
pub mod symbol_provider;
pub mod symbols;
mod wgsl;
#[cfg(test)]
mod tests {
use std::{
collections::HashSet,
path::{Path, PathBuf},
};
use regex::Regex;
use crate::{
include::IncludeHandler,
position::{ShaderFilePosition, ShaderFileRange, ShaderPosition},
shader::{
GlslShadingLanguageTag, HlslShadingLanguageTag, ShaderCompilationParams, ShaderParams,
ShaderStage, ShadingLanguage, ShadingLanguageTag, WgslShadingLanguageTag,
},
shader_error::ShaderError,
symbols::{
intrinsics::ShaderIntrinsics, shader_module_parser::ShaderModuleParser,
symbol_list::ShaderSymbolList, symbols::ShaderSymbolData,
},
};
use super::symbol_provider::{default_include_callback, SymbolProvider};
pub fn find_file_dependencies(
include_handler: &mut IncludeHandler,
shader_content: &String,
) -> Vec<PathBuf> {
let include_regex = Regex::new("\\#include\\s+\"([\\w\\s\\\\/\\.\\-]+)\"").unwrap();
let dependencies_paths: Vec<&str> = include_regex
.captures_iter(&shader_content)
.map(|c| c.get(1).unwrap().as_str())
.collect();
dependencies_paths
.iter()
.filter_map(|dependency| include_handler.search_path_in_includes(Path::new(dependency)))
.collect::<Vec<PathBuf>>()
}
pub fn find_dependencies(
include_handler: &mut IncludeHandler,
shader_content: &String,
) -> HashSet<(String, PathBuf)> {
let dependencies_path = find_file_dependencies(include_handler, shader_content);
let dependencies = dependencies_path
.into_iter()
.map(|e| (std::fs::read_to_string(&e).unwrap(), e))
.collect::<Vec<(String, PathBuf)>>();
let mut recursed_dependencies = HashSet::new();
for dependency in &dependencies {
recursed_dependencies.extend(find_dependencies(include_handler, &dependency.0));
}
recursed_dependencies.extend(dependencies);
recursed_dependencies
}
fn get_all_preprocessed_symbols<T: ShadingLanguageTag>(
shader_module_parser: &mut ShaderModuleParser,
symbol_provider: &SymbolProvider,
file_path: &Path,
shader_content: &String,
) -> Result<ShaderSymbolList, ShaderError> {
let mut include_handler = IncludeHandler::main_without_config(&file_path);
let deps = find_dependencies(&mut include_handler, &shader_content);
let mut all_symbols = ShaderIntrinsics::get(T::get_language())
.get_intrinsics_symbol(&ShaderCompilationParams::default())
.to_owned();
let shader_module = shader_module_parser
.create_module(file_path, shader_content)
.unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams::default(),
&mut default_include_callback::<T>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
all_symbols.append(symbols.into());
for dep in deps {
let shader_module = shader_module_parser.create_module(&dep.1, &dep.0).unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams::default(),
&mut default_include_callback::<T>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
all_symbols.append(symbols.into());
}
Ok(all_symbols)
}
#[test]
fn intrinsics_glsl_ok() {
let _ = ShaderSymbolList::parse_from_json(String::from(include_str!(
"glsl/glsl-intrinsics.json"
)));
}
#[test]
fn intrinsics_hlsl_ok() {
let _ = ShaderSymbolList::parse_from_json(String::from(include_str!(
"hlsl/hlsl-intrinsics.json"
)));
}
#[test]
fn intrinsics_wgsl_ok() {
let _ = ShaderSymbolList::parse_from_json(String::from(include_str!(
"wgsl/wgsl-intrinsics.json"
)));
}
#[test]
fn create_glsl_module_ok() {
let mut parser = ShaderModuleParser::glsl();
let path = Path::new("./test/glsl/ok.frag.glsl");
let _module = parser.create_module(path, &std::fs::read_to_string(path).unwrap());
}
#[test]
fn create_hlsl_module_ok() {
let mut parser = ShaderModuleParser::hlsl();
let path = Path::new("./test/hlsl/ok.hlsl");
let _module = parser.create_module(path, &std::fs::read_to_string(path).unwrap());
}
#[test]
fn create_wgsl_module_ok() {
let mut parser = ShaderModuleParser::wgsl();
let path = Path::new("./test/wgsl/ok.wgsl");
let _module = parser.create_module(path, &std::fs::read_to_string(path).unwrap());
}
#[test]
fn symbols_glsl_ok() {
let file_path = Path::new("./test/glsl/include-level.comp.glsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let mut shader_module_parser =
ShaderModuleParser::from_shading_language(ShadingLanguage::Glsl);
let symbol_provider = SymbolProvider::from_shading_language(ShadingLanguage::Glsl);
let shader_module = shader_module_parser
.create_module(file_path, &shader_content)
.unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams::default(),
&mut default_include_callback::<GlslShadingLanguageTag>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
assert!(!symbols.functions.is_empty());
}
#[test]
fn symbols_hlsl_ok() {
let file_path = Path::new("./test/hlsl/include-level.hlsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let mut shader_module_parser =
ShaderModuleParser::from_shading_language(ShadingLanguage::Hlsl);
let symbol_provider = SymbolProvider::from_shading_language(ShadingLanguage::Hlsl);
let shader_module = shader_module_parser
.create_module(file_path, &shader_content)
.unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams::default(),
&mut default_include_callback::<HlslShadingLanguageTag>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
assert!(!symbols.functions.is_empty());
}
#[test]
fn symbols_wgsl_ok() {
let file_path = Path::new("./test/wgsl/ok.wgsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let mut shader_module_parser =
ShaderModuleParser::from_shading_language(ShadingLanguage::Wgsl);
let symbol_provider = SymbolProvider::from_shading_language(ShadingLanguage::Wgsl);
let shader_module = shader_module_parser
.create_module(file_path, &shader_content)
.unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams::default(),
&mut default_include_callback::<WgslShadingLanguageTag>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
assert!(symbols.functions.is_empty());
}
#[test]
fn symbol_scope_glsl_ok() {
let file_path = Path::new("./test/glsl/scopes.frag.glsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let mut shader_module_parser =
ShaderModuleParser::from_shading_language(ShadingLanguage::Glsl);
let symbol_provider = SymbolProvider::from_shading_language(ShadingLanguage::Glsl);
let preprocessed_symbol_list = get_all_preprocessed_symbols::<GlslShadingLanguageTag>(
&mut shader_module_parser,
&symbol_provider,
file_path,
&shader_content,
)
.unwrap();
let symbol_list = preprocessed_symbol_list.as_ref();
let symbols = symbol_list.filter_scoped_symbol(&ShaderFilePosition::new(
PathBuf::from(file_path),
16,
0,
));
let variables_visibles: Vec<String> = vec![
"scopeRoot".into(),
"scope1".into(),
"scopeGlobal".into(),
"level1".into(),
];
let variables_not_visibles: Vec<String> = vec!["scope2".into(), "testData".into()];
for variable_visible in variables_visibles {
assert!(
symbols
.variables
.iter()
.any(|e| e.label == variable_visible),
"Failed to find variable {} {:#?}",
variable_visible,
symbols.variables
);
}
for variable_not_visible in variables_not_visibles {
assert!(
!symbols
.variables
.iter()
.any(|e| e.label == variable_not_visible),
"Found variable {}",
variable_not_visible
);
}
}
#[test]
fn uniform_glsl_ok() {
let file_path = Path::new("./test/glsl/uniforms.frag.glsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let mut shader_module_parser =
ShaderModuleParser::from_shading_language(ShadingLanguage::Glsl);
let symbol_provider = SymbolProvider::from_shading_language(ShadingLanguage::Glsl);
let shader_module = shader_module_parser
.create_module(file_path, &shader_content)
.unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams::default(),
&mut default_include_callback::<GlslShadingLanguageTag>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
assert!(symbols
.types
.iter()
.find(|e| e.label == "MatrixHidden")
.is_some());
assert!(symbols
.variables
.iter()
.find(|e| e.label == "u_accessor"
&& match &e.data {
ShaderSymbolData::Variables { ty, count: _ } => ty == "MatrixHidden",
_ => false,
})
.is_some());
assert!(symbols
.variables
.iter()
.find(|e| e.label == "u_modelviewGlobal")
.is_some());
assert!(symbols
.variables
.iter()
.find(|e| e.label == "u_modelviewHidden")
.is_none());
}
#[test]
fn test_position_conversion() {
fn test_to_byte_offset(
shader_content: &str,
expected_content: &str,
position: &ShaderPosition,
) -> usize {
let byte_offset = position.to_byte_offset(&shader_content).unwrap();
if expected_content.len() > 0 {
let content_from_offset = &shader_content[byte_offset..];
assert!(content_from_offset.len() >= expected_content.len());
assert!(
content_from_offset == expected_content,
"Offseted content {:?} with offset {} is incorrect.",
&shader_content[byte_offset..],
byte_offset
);
} else {
assert!(byte_offset == shader_content.len());
}
byte_offset
}
fn test_back_to_position(
shader_content: &str,
expected_position: &ShaderPosition,
byte_offset: usize,
) {
let converted_position =
ShaderPosition::from_byte_offset(&shader_content, byte_offset).unwrap();
let converted_byte_offset = converted_position.to_byte_offset(&shader_content).unwrap();
assert!(converted_position == *expected_position, "Position {:#?} with byte offset {} is different from converted position: {:#?} with byte offset {}", expected_position, byte_offset, converted_position, converted_byte_offset);
}
let utf8_file_path = Path::new("./test/hlsl/utf8.hlsl");
let utf8_shader_content = std::fs::read_to_string(utf8_file_path).unwrap();
let test_data = vec![
("\n}", ShaderPosition::new(5, 0), &utf8_shader_content),
("", ShaderPosition::new(6, 1), &utf8_shader_content),
(
"id main() {\n\n}",
ShaderPosition::new(4, 2),
&utf8_shader_content,
),
(
"にちは世界!\n\nvoid main() {\n\n}",
ShaderPosition::new(2, 5),
&utf8_shader_content,
),
];
for (index, (expected_content, position, shader_content)) in test_data.iter().enumerate() {
println!("Testing conversion {} for {:?}", index, position);
println!(
"Content: {:?} (len {})",
shader_content,
shader_content.len()
);
let byte_offset = test_to_byte_offset(&shader_content, expected_content, &position);
println!("Found byte_offset {}", byte_offset);
test_back_to_position(&shader_content, &position, byte_offset);
}
}
#[test]
fn test_end_range() {
let file_path = Path::new("./test/hlsl/utf8.hlsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let range = ShaderFileRange::whole(file_path.into(), &shader_content);
println!("File range: {:#?}", range);
let end_byte_offset = range.range.end.to_byte_offset(&shader_content).unwrap();
assert!(end_byte_offset == shader_content.len());
}
#[test]
fn test_intrinsic_filtering() {
let intrinsics = ShaderIntrinsics::get(ShadingLanguage::Hlsl);
let intrinsics_frag = intrinsics.get_intrinsics_symbol(&ShaderCompilationParams {
shader_stage: Some(ShaderStage::Fragment),
..Default::default()
});
assert!(
intrinsics_frag.find_symbol("clip").is_some(),
"clip() should be available from fragment shader."
);
let intrinsics_common =
intrinsics.get_intrinsics_symbol(&ShaderCompilationParams::default());
assert!(
intrinsics_common.find_symbol("clip").is_some(),
"clip() should be available if no shader given."
);
let intrinsics_vert = intrinsics.get_intrinsics_symbol(&ShaderCompilationParams {
shader_stage: Some(ShaderStage::Vertex),
..Default::default()
});
assert!(
intrinsics_vert.find_symbol("clip").is_none(),
"clip() should not be available from vertex shader."
);
}
#[test]
fn test_macro_expansion() {
let file_path = Path::new("./test/hlsl/macro-struct.hlsl");
let shader_content = std::fs::read_to_string(file_path).unwrap();
let mut shader_module_parser =
ShaderModuleParser::from_shading_language(ShadingLanguage::Hlsl);
let symbol_provider = SymbolProvider::from_shading_language(ShadingLanguage::Hlsl);
let shader_module = shader_module_parser
.create_module(file_path, &shader_content)
.unwrap();
let symbols = symbol_provider
.query_symbols(
&shader_module,
ShaderParams {
compilation: ShaderCompilationParams {
experimental_macro_expansion: true,
..Default::default()
},
..Default::default()
},
&mut default_include_callback::<HlslShadingLanguageTag>,
None,
)
.unwrap();
let symbols = symbols.get_all_symbols();
assert!(symbols
.types
.iter()
.find(|t| t.label == "ProceduralTestValue0")
.is_some());
assert!(symbols
.types
.iter()
.find(|t| t.label == "ProceduralTestValue1")
.is_some());
assert!(symbols
.types
.iter()
.find(|t| t.label == "TestMacro")
.is_some());
}
}