1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use gl::types::*;
use glin;
use regex::Regex;
use std::io::Read;
use util::{AutoLoader, Result, Error};
use std::fs;
use std::path::Path;
use std::error;
use glin::CreationContext;

#[derive(Clone,Copy)]
pub enum ShaderPrecision{
    Low,
    Medium,
    High,
}

impl ToString for ShaderPrecision{
    fn to_string(&self) -> String{
        match self{
            &ShaderPrecision::Low => "lowp".to_string(),
            &ShaderPrecision::Medium => "mediump".to_string(),
            &ShaderPrecision::High => "highp".to_string(),
        }
    }
}

#[derive(Clone)]
pub struct ProgramSettings<P: AsRef<Path> + Clone>{
    pub version: usize,
    pub precission: ShaderPrecision,
    pub defines: Vec<(String, String)>,
    pub shaders: Vec<(GLenum, P)>,
}

fn substitute_define(shader_src: &str, define: &str, value: &str) -> String{
    let search = r"#define\s*.*".to_string() + define + ".*";
    let replace = "#define ".to_string() + define + " " + value + "\n";
    let re = Regex::new(&search).unwrap();
    if re.is_match(shader_src){
        re.replace(shader_src, replace.as_str()).to_string()
    }else{
        replace + shader_src
    }
}

fn split_shader_header(shader_src: &str) -> (String,String){
    let re_version = Regex::new(r"^\b*\#version\s+[0-9]*\s*").unwrap();
    let re_precision = Regex::new(r"^\b*precision\s+(lowp|mediump|highp)\s+float;\s*").unwrap();
    let mut header = String::new();
    let mut source = String::new();
    for line in shader_src.lines(){
        if re_version.is_match(line){
            header.push_str(line);
            header.push('\n');
        }else if re_precision.is_match(line){
            header.push_str(line);
            header.push('\n');
        }else{
            source.push_str(line);
            source.push('\n');
        }
    }
    (header, source)
}

fn replace_version_precision(shader_header: &str, version: usize, precision: ShaderPrecision) -> String{
    let re_version = Regex::new(r"^\b*\#version\s+[0-9]+").unwrap();
    let re_precision = Regex::new(r"^\b*precision\s+(lowp|mediump|highp)\s+float;").unwrap();
    let version_str = "#version ".to_string() + &version.to_string() + "\n";
    let precission_str = "precision ".to_string() + &precision.to_string() + " float;\n";
    let shader_header = if re_version.find(shader_header).is_some() {
        re_version.replace(shader_header, version_str.as_str()).to_string()
    }else{
        version_str + "\n" + shader_header
    };

    if re_precision.find(&shader_header).is_some(){
        re_precision.replace(&shader_header, precission_str.as_str()).to_string()
    }else{
        shader_header + "\n" + &precission_str
    }
}

fn load_program<P: AsRef<Path> + Clone>(gl: &::CreationProxy, settings: ProgramSettings<P>) -> Result<glin::Program>{
    let mut errors = vec![];
    let shaders = settings.shaders.iter().filter_map(|&(ty, ref path)| {
        let mut shader_src = String::new();
        let shader_file = fs::File::open(path).map_err(|err| (&err as &error::Error).description().to_string());
        if let Ok(mut shader_file) = shader_file{
            let read = shader_file.read_to_string(&mut shader_src).map_err(|err| (&err as &error::Error).description().to_string());
            if let Ok(_) = read{
                let (header, src) = split_shader_header(&shader_src);
                shader_src = settings.defines.iter().fold(src, |shader_src, &(ref define, ref value)|{
                    substitute_define(&shader_src, define, value)
                });
                let header = replace_version_precision(&header, settings.version, settings.precission);
                shader_src = header + &shader_src;
                // println!("\n\n\n---------------------------------------------");
                // println!("{}", shader_src);
                Some((ty, shader_src.clone()))
            }else{
                errors.push(read.unwrap_err());
                None
            }
        }else{
            errors.push(shader_file.unwrap_err());
            None
        }
    }).collect::<Vec<_>>();

    if errors.is_empty(){
        let shaders = shaders.iter().map(|&(ty,ref shaders)| (ty,shaders.as_str())).collect::<Vec<_>>();
        gl.new_program().from_src_bindings(&shaders, &::default_attribute_bindings())
            .map_err(|e| Error::with_cause("Program load error",e))
    }else{
        Err(Error::new(&errors.join("\n\n")))
    }
}

fn parse_includes(shader_src: &str) -> Vec<String>{
    let search = r"#include\s*".to_string() + "\"(.*)\"";
    let re = Regex::new(&search).unwrap();
    re.captures_iter(shader_src).map(|captures|{
        captures[1].to_owned()
    }).collect()
}


pub(crate) fn new_program<P: AsRef<Path> + Clone + 'static>(gl: ::CreationProxy, settings: ProgramSettings<P>) -> AutoLoader<glin::Program>{
    let mut paths = settings.shaders
        .iter()
        .map(|&(_ty,ref path)| path.as_ref().to_str().unwrap().to_string())
        .collect::<Vec<_>>();

    let includes = paths.iter().flat_map(|path|{
        if let Ok(mut shader) = fs::File::open(path){
            let mut shader_src = String::new();
            shader.read_to_string(&mut shader_src).unwrap();
            parse_includes(&shader_src)
        }else{
            vec![]
        }
    }).collect::<Vec<_>>();

    paths.extend_from_slice(&includes);

    AutoLoader::new(
        paths,
        move || load_program(&gl, settings.clone())
    )
}