use super::*;
use crate::*;
pub struct OglProg {
pub prog: glow::Program,
pub gl: GlowGL,
}
impl OglProg {
#[allow(clippy::clone_on_copy)]
pub fn prog(&self) -> glow::Program {
self.prog.clone()
}
pub fn compile_program(gl: &GlowGL, raw_source: &str) -> Result<OglProg, CompilationError> {
let tokens = tokensize_source(raw_source);
let header = get_source_block(&tokens, "HEADER");
let uniforms = get_source_block(&tokens, "UNIFORMS");
let vertex_attributes = get_source_block(&tokens, "VERTEX_ATTRIBUTES");
let vertex_shader = get_source_block(&tokens, "VERTEX_SHADER");
let fragment_shader = get_source_block(&tokens, "FRAGMENT_SHADER");
let vertex_shader_module = gen_shader_module(
&tokens,
vec![header, uniforms, vertex_attributes, vertex_shader],
);
let fragment_shader_module =
gen_shader_module(&tokens, vec![header, uniforms, fragment_shader]);
let mut module_list = vec![
(glow::VERTEX_SHADER, vertex_shader_module),
(glow::FRAGMENT_SHADER, fragment_shader_module),
];
let shader_iterator = module_list.iter_mut().filter(|(_, opt)| opt.is_some()).map(
|(shader_type, opt)| unsafe {
let source = opt.take().unwrap();
let shader: glow::Shader = gl.create_shader(*shader_type).unwrap();
gl.shader_source(shader, source.as_str());
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
let compile_error = gl.get_shader_info_log(shader);
Err(CompilationError::ShaderError {
ogl_error: compile_error,
faulty_source: source,
})
} else {
Ok(shader)
}
},
);
#[allow(clippy::clone_on_copy)]
let program = unsafe {
let mut shaders = Vec::new();
let program = gl.create_program().unwrap();
for shader_res in shader_iterator {
shaders.push(shader_res?);
let cur_shader: glow::Shader = shaders.last().unwrap().clone();
gl.attach_shader(program, cur_shader);
}
gl.link_program(program);
if !gl.get_program_link_status(program) {
let ogl_error = gl.get_program_info_log(program);
return Err(CompilationError::ShaderError {
ogl_error,
faulty_source: String::new(),
});
}
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
}
OglProg {
gl: gl.clone(),
prog: program,
}
};
Ok(program)
}
}
impl Drop for OglProg {
fn drop(&mut self) {
unsafe {
self.gl.delete_program(self.prog);
}
}
}
impl Bindable for OglProg {
fn bind(&self, opt: bool) {
let gl = &self.gl;
unsafe { gl.use_program(opt.map(|| self.prog())) }
}
}
#[derive(Debug)]
pub enum CompilationError {
ShaderError {
ogl_error: String,
faulty_source: String,
},
LinkError {
ogl_error: String,
faulty_source: String,
},
}
enum SlToken {
If(String),
Ifndef(String),
Define(String),
Source(String),
Endif,
}
impl ToString for SlToken {
fn to_string(&self) -> String {
match self {
SlToken::If(text) => format!("#if {}\n", text),
SlToken::Ifndef(text) => format!("#ifndef {}\n", text),
SlToken::Define(text) => format!("#define {}\n", text),
SlToken::Source(text) => text.to_string(),
SlToken::Endif => "endif\n".to_string(),
}
}
}
#[derive(PartialEq)]
enum LexState {
Start,
If,
Ifndef,
Endif,
Define,
}
fn tokensize_source(raw_source: &str) -> Vec<SlToken> {
let mut tokens = Vec::new();
let mut state = LexState::Start;
let mut source_iter = raw_source.chars().peekable();
let mut accum = String::new();
let push_source = |accum: &mut String, tokens: &mut Vec<_>| {
let all_whitespece = accum.chars().all(|a| a.is_whitespace());
if !accum.is_empty() && !all_whitespece {
tokens.push(SlToken::Source(accum.clone()));
}
accum.clear();
};
while source_iter.peek().is_some() || !accum.is_empty() || state != LexState::Start {
let c = source_iter.next().unwrap_or('\0');
match state {
LexState::Start => {
if let ('#', true, _iter) = lookahead(c, "ifndef", source_iter.clone()) {
source_iter = _iter;
state = LexState::Ifndef;
push_source(&mut accum, &mut tokens);
} else if let ('#', true, _iter) = lookahead(c, "define", source_iter.clone()) {
source_iter = _iter;
state = LexState::Define;
push_source(&mut accum, &mut tokens);
} else if let ('#', true, _iter) = lookahead(c, "endif", source_iter.clone()) {
source_iter = _iter;
state = LexState::Endif;
push_source(&mut accum, &mut tokens);
} else if let ('#', true, _iter) = lookahead(c, "if", source_iter.clone()) {
source_iter = _iter;
state = LexState::If;
push_source(&mut accum, &mut tokens);
} else {
accum.push(c);
}
}
LexState::Ifndef => {
if c == '\n' || c == '\0' {
state = LexState::Start;
tokens.push(SlToken::Ifndef(accum.clone()));
accum.clear();
} else if c.is_alphanumeric() || c == '_' {
accum.push(c);
}
}
LexState::If => {
if c == '\n' || c == '\0' {
state = LexState::Start;
tokens.push(SlToken::If(accum.clone()));
accum.clear();
} else if c.is_alphanumeric() || c == '_' {
accum.push(c);
}
}
LexState::Define => {
if c == '\n' || c == '\0' {
state = LexState::Start;
tokens.push(SlToken::Define(accum.clone()));
accum.clear();
} else if c.is_alphanumeric() || c == '_' {
accum.push(c);
}
}
LexState::Endif => {
if c == '\n' || c == '\0' {
state = LexState::Start;
tokens.push(SlToken::Endif);
accum.clear();
}
}
}
}
tokens
}
fn lookahead<T>(c: char, ident: &'static str, mut iter: T) -> (char, bool, T)
where
T: std::iter::Iterator<Item = char> + Sized,
{
let eq_test = (&mut iter).take(ident.len()).collect::<String>() == ident;
(c, eq_test, iter)
}
fn get_source_block(tokens: &[SlToken], block_ident: &'static str) -> Option<(usize, usize)> {
let mut found_ifndef = false;
let query = tokens
.iter()
.enumerate()
.filter(|&(_, tok)| {
if let SlToken::Ifndef(text) = tok {
if text == block_ident {
found_ifndef = true;
true
} else {
false
}
} else if let SlToken::Endif = tok {
found_ifndef
} else {
false
}
})
.take(2)
.map(|(index, _)| index)
.collect::<Vec<_>>();
if query.len() == 2 {
Some((query[0], query[1]))
} else {
None
}
}
fn gen_shader_module(
tokens: &[SlToken],
code_blocks: Vec<Option<(usize, usize)>>,
) -> Option<String> {
let mut source = String::new();
tokens.last()?;
code_blocks
.iter()
.copied()
.flatten()
.flat_map(|(lbound, ubound)| tokens.iter().take(ubound).skip(lbound + 1))
.for_each(|tok| {
source.push_str(tok.to_string().as_str());
});
Some(source)
}