use alloc::string::String;
use alloc::vec::Vec;
use alloc::btree_map::BTreeMap;
use core::str::{self, Utf8Error};
use core::ptr;
use core::ops::Drop;
use regex::Regex;
use gl;
use gl::types::*;
use super::{AttributeKind, UniformKind, GLAttribute, GLUniform};
pub struct GLProgram {
id: GLuint,
uniforms: BTreeMap<String, GLUniform>,
attributes: BTreeMap<String, GLAttribute>,
}
impl GLProgram {
#[inline]
pub fn new(vertex: &str, fragment: &str) -> Self {
let mut program = GLProgram {
id: 0,
uniforms: BTreeMap::new(),
attributes: BTreeMap::new(),
};
program.set(vertex, fragment);
program
}
#[inline]
pub fn new_mutiple(vertex: &[&str], fragment: &[&str]) -> Self {
let mut program = GLProgram {
id: 0,
uniforms: BTreeMap::new(),
attributes: BTreeMap::new(),
};
program.set_mutiple(vertex, fragment);
program
}
#[inline(always)]
pub fn id(&self) -> GLuint { self.id }
#[inline]
pub fn bind(&self) -> &Self {
unsafe { gl::UseProgram(self.id); }
self
}
#[inline]
pub fn unbind(&self) -> &Self {
unsafe { gl::UseProgram(0); }
self
}
#[inline(always)]
pub fn has_uniform(&self, name: &str) -> bool {
self.uniforms.contains_key(name)
}
#[inline(always)]
pub fn get_uniform(&self, name: &str) -> &GLUniform {
match self.uniforms.get(name) {
Some(ref uniform) => uniform,
None => panic!("No uniform named {:?} found", name),
}
}
#[inline(always)]
pub fn uniforms(&self) -> &BTreeMap<String, GLUniform> {&self.uniforms}
#[inline(always)]
pub fn uniforms_mut(&mut self) -> &mut BTreeMap<String, GLUniform> {&mut self.uniforms}
#[inline(always)]
pub fn has_attribute(&self, name: &str) -> bool {
self.attributes.contains_key(name)
}
#[inline(always)]
pub fn get_attribute(&self, name: &str) -> &GLAttribute {
match self.attributes.get(name) {
Some(ref attribute) => attribute,
None => panic!("No attribute named {:?} found", name),
}
}
#[inline(always)]
pub fn attributes(&self) -> &BTreeMap<String, GLAttribute> {&self.attributes}
#[inline(always)]
pub fn attributes_mut(&mut self) -> &mut BTreeMap<String, GLAttribute> {&mut self.attributes}
#[inline]
pub fn set(&mut self, vertex: &str, fragment: &str) -> &mut Self {
let vs = compile_shader(vertex, gl::VERTEX_SHADER);
let fs = compile_shader(fragment, gl::FRAGMENT_SHADER);
let id = link_program(vs, fs);
self.set_program_id(id)
}
#[inline]
pub fn set_mutiple(&mut self, vertex: &[&str], fragment: &[&str]) -> &mut Self {
let vs = compile_shaders(vertex, gl::VERTEX_SHADER);
let fs = compile_shaders(fragment, gl::FRAGMENT_SHADER);
let id = link_program(vs, fs);
self.set_program_id(id)
}
#[inline]
fn set_program_id(&mut self, id: GLuint) -> &mut Self {
{
let ref mut uniforms = self.uniforms;
let ref mut attributes = self.attributes;
if self.id != 0 {
uniforms.clear();
attributes.clear();
unsafe { gl::DeleteProgram(self.id); }
}
self.id = id;
parse_uniforms(id, uniforms);
parse_attributes(id, attributes);
}
self
}
}
impl Drop for GLProgram {
#[inline]
fn drop(&mut self) {
if self.id != 0 {
unsafe { gl::DeleteProgram(self.id); }
}
}
}
#[inline]
fn parse_uniforms(program: GLuint, uniforms: &mut BTreeMap<String, GLUniform>) {
let mut max_length = 0;
let mut active_length = 0;
unsafe {
gl::GetProgramiv(program, gl::ACTIVE_UNIFORM_MAX_LENGTH, &mut max_length);
gl::GetProgramiv(program, gl::ACTIVE_UNIFORMS, &mut active_length);
}
for i in 0..active_length {
let mut length = 0;
let mut count = 0;
let mut kind = 0;
let mut buf = Vec::with_capacity(max_length as usize);
let location;
unsafe {
let buf_ptr = buf.as_mut_ptr() as *mut GLchar;
gl::GetActiveUniform(program, i as GLuint, max_length, &mut length, &mut count, &mut kind, buf_ptr);
buf.set_len(length as usize);
location = gl::GetUniformLocation(program, buf_ptr);
}
let mut name = match string_from_utf8(&buf) {
Ok(string) => string,
Err(vec) => panic!("Could not convert uniform name from buffer: {:?}", vec)
};
if name.chars().nth(name.len() - 1).expect("Unexpected empty uniform name") == ']' {
let new_name = match
Regex::new(r"(.*)\[\d+\]")
.expect("regex failed to compile")
.captures(&name) {
Some(cap) => match cap.get(1) {
Some(name) => Some(String::from(name.as_str())),
None => None,
},
None => None,
};
if let Some(value) = new_name {
name = value;
}
}
uniforms.insert(name, GLUniform::new(UniformKind::from_gl(kind), count as usize, location as usize));
}
}
#[inline]
fn string_from_utf8(vec: &Vec<u8>) -> Result<String, Utf8Error> {
match str::from_utf8(vec) {
Ok(s) => Ok(String::from(s)),
Err(e) => Err(e),
}
}
#[inline]
fn parse_attributes(program: GLuint, attributes: &mut BTreeMap<String, GLAttribute>) {
let mut max_length = 0;
let mut active_length = 0;
unsafe {
gl::GetProgramiv(program, gl::ACTIVE_ATTRIBUTE_MAX_LENGTH, &mut max_length);
gl::GetProgramiv(program, gl::ACTIVE_ATTRIBUTES, &mut active_length);
}
for i in 0..active_length {
let mut length = 0;
let mut count = 0;
let mut kind = 0;
let mut buf = Vec::with_capacity(max_length as usize);
let buf_ptr = buf.as_mut_ptr() as *mut GLchar;
let location;
unsafe {
gl::GetActiveAttrib(program, i as u32, max_length, &mut length, &mut count, &mut kind, buf_ptr);
buf.set_len(length as usize);
location = gl::GetAttribLocation(program, buf_ptr);
}
let name = match string_from_utf8(&buf) {
Ok(string) => string,
Err(vec) => panic!("Could not convert attribute name from buffer: {:?}", vec)
};
attributes.insert(name, GLAttribute::new(AttributeKind::from_gl(kind), count as usize, location as usize));
}
}
#[inline]
pub fn link_program(vertex_shader: GLuint, fragment_shader: GLuint) -> GLuint {
let program = unsafe { gl::CreateProgram() };
unsafe {
gl::AttachShader(program, vertex_shader);
gl::DeleteShader(vertex_shader);
gl::AttachShader(program, fragment_shader);
gl::DeleteShader(fragment_shader);
gl::LinkProgram(program);
gl::ValidateProgram(program);
gl::UseProgram(program);
}
check_program_status(program)
}
#[inline]
pub fn check_program_status(program: GLuint) -> GLuint {
let mut status = 0;
unsafe { gl::GetProgramiv(program, gl::LINK_STATUS, &mut status) };
if status != (gl::TRUE as GLint) {
let mut len: GLint = 0;
unsafe { gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len); }
let mut buf = Vec::with_capacity(len as usize);
unsafe {
buf.set_len(len as usize);
gl::GetProgramInfoLog(program, len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);
}
panic!("{}", str::from_utf8(&buf).ok().expect("ProgramInfoLog not valid utf8"));
}
program
}
#[inline]
pub fn compile_shader(source: &str, kind: GLenum) -> GLuint {
let shader = unsafe { gl::CreateShader(kind) };
unsafe {
let ptr: *const GLchar = source.as_bytes().as_ptr() as *const GLchar;
let len = source.len() as GLint;
gl::ShaderSource(shader, 1, &ptr, &len);
gl::CompileShader(shader);
}
check_shader_status(shader)
}
#[inline]
pub fn compile_shaders(sources: &[&str], kind: GLenum) -> GLuint {
let shader = unsafe { gl::CreateShader(kind) };
unsafe {
for source in sources.iter() {
let ptr: *const GLchar = source.as_bytes().as_ptr() as *const GLchar;
let len = source.len() as GLint;
gl::ShaderSource(shader, 1, &ptr, &len);
gl::CompileShader(shader);
}
}
check_shader_status(shader)
}
#[inline]
pub fn check_shader_status(shader: GLuint) -> GLuint {
let mut status = 0;
unsafe { gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status) };
if status != (gl::TRUE as GLint) {
let mut len = 0;
unsafe { gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len); }
let mut buf = Vec::with_capacity(len as usize);
unsafe {
buf.set_len(len as usize);
gl::GetShaderInfoLog(shader, len, ptr::null_mut(), buf.as_mut_ptr() as *mut GLchar);
}
panic!("{}", str::from_utf8(&buf).ok().expect("ShaderInfoLog not valid utf8"));
}
shader
}