use std::ptr;
use std::str::{self, Utf8Error};
use fnv::FnvHashMap;
use gl;
use gl::types::*;
use regex::Regex;
use super::{GLAttribute, GLUniform};
#[derive(Debug)]
pub struct GLProgram {
id: GLuint,
uniforms: FnvHashMap<String, GLUniform>,
attributes: FnvHashMap<String, GLAttribute>,
}
impl GLProgram {
#[inline]
pub fn new(vertex: &str, fragment: &str) -> Self {
let mut program = GLProgram {
id: 0,
uniforms: FnvHashMap::default(),
attributes: FnvHashMap::default(),
};
program.set(vertex, fragment);
program
}
#[inline]
pub fn new_mutiple(vertex: &[&str], fragment: &[&str]) -> Self {
let mut program = GLProgram {
id: 0,
uniforms: FnvHashMap::default(),
attributes: FnvHashMap::default(),
};
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) -> &FnvHashMap<String, GLUniform> {
&self.uniforms
}
#[inline(always)]
pub fn uniforms_mut(&mut self) -> &mut FnvHashMap<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) -> &FnvHashMap<String, GLAttribute> {
&self.attributes
}
#[inline(always)]
pub fn attributes_mut(&mut self) -> &mut FnvHashMap<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 FnvHashMap<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(kind.into(), 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 FnvHashMap<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(kind.into(), 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
}