use std::ffi::CString; use std::fs::File; use std::io::Read; use std::path::Path; use std::ptr::{null, null_mut}; use std::str::from_utf8; use crate::{ error::Error, matrix::Matrix4f, misc::{AsEnum, Bindable}, vector::{Vector2f, Vector4f}, }; /* Programm */ #[derive(Default)] pub struct Program { id: gl::GLuint, } impl Program { pub fn builder() -> Builder { Builder::default() } pub fn from_shaders(iter: I) -> Result where I: IntoIterator, { Self::from_shaders_result(iter.into_iter().map(Ok)) } pub fn from_shaders_result(iter: I) -> Result where I: IntoIterator>, E: From, { let mut builder = Self::builder(); for shader in iter.into_iter() { builder = builder.add_shader(shader?); } let program = builder.build()?; Ok(program) } pub fn id(&self) -> gl::GLuint { self.id } pub fn uniform(&self, location: T, uniform: Uniform<'_>) -> Result<(), Error> where T: IntoUniformLocation, { let location = IntoUniformLocation::into_location(location, &self)?; match uniform { Uniform::Matrix4f(v) => gl::uniform_matrix_4fv(location, 1, gl::FALSE, v.as_ptr()), Uniform::Vector4f(v) => gl::uniform_4fv(location, 1, v.as_ptr()), Uniform::Vector2f(v) => gl::uniform_2fv(location, 1, v.as_ptr()), Uniform::Float(v) => gl::uniform_1f(location, v), Uniform::Texture(i) => gl::uniform_1i(location, i), Uniform::TextureVec(i) => gl::uniform_1iv(location, i.len() as _, i.as_ptr()), } Ok(()) } pub fn uniform_location(&self, name: T) -> Result where T: Into, { let name = Into::::into(name); let name = CString::new(name)?; let location = Error::checked(|| gl::get_uniform_location(self.id, name.as_ptr()))?; if location >= 0 { Ok(location) } else { Err(Error::ShaderUnknownUniform(name.into_string().unwrap())) } } pub fn uniform_block_index(&self, name: T) -> Result where T: Into, { let name = Into::::into(name); let name = CString::new(name)?; let location = Error::checked(|| gl::get_uniform_block_index(self.id, name.as_ptr()))?; if location != gl::INVALID_INDEX { Ok(location) } else { Err(Error::ShaderUnknownUniform(name.into_string().unwrap())) } } pub fn uniform_block_binding(&self, index: T, binding: gl::GLuint) -> Result<(), Error> where T: IntoUniformBlockIndex, { let index = IntoUniformBlockIndex::into_index(index, &self)?; gl::uniform_block_binding(self.id, index, binding); Ok(()) } } impl Drop for Program { fn drop(&mut self) { gl::delete_program(self.id); } } impl Bindable for Program { fn bind(&self) { gl::use_program(self.id); } fn unbind(&self) { gl::use_program(0); } } /* Shader */ pub struct Shader { id: gl::GLuint, } impl Shader { pub fn from_string(type_: Type, mut source: String, f: F) -> Result where F: Fn(&str) -> Result, { while let Some(p) = source.find("#pragma include ") { let s = &source[p..]; let e = s.find('\n').unwrap_or_else(|| s.len()); let s = &s[..e]; let c = f(&s[16..])?; source = source.replace(s, &c); } let id = gl::create_shader(type_.as_enum()); let id = Error::err_if(&0, id)?; let source = CString::new(source)?; let ptr = source.as_ptr(); let ptr: *const *const _ = &ptr; gl::shader_source(id, 1, ptr as *const *const _, null()); gl::compile_shader(id); let mut success = 1; gl::get_shader_iv(id, gl::COMPILE_STATUS, &mut success); if success != 1 { let mut len = 0; gl::get_shader_iv(id, gl::INFO_LOG_LENGTH, &mut len); let mut buffer = Vec::::with_capacity(len as usize + 1); buffer.resize(len as usize, 0); gl::get_shader_info_log( id, len, null_mut(), buffer.as_mut_ptr() as *mut gl::types::GLchar, ); let msg = from_utf8(&buffer)?; let code = source.into_string().unwrap(); let code = code .lines() .enumerate() .map(|(i, s)| format!("{:03}: {}\n", i + 1, s)) .collect(); return Err(Error::ShaderCompile { code, error: msg.into(), }); } Ok(Self { id }) } pub fn from_reader(type_: Type, reader: &mut R, f: F) -> Result where R: Read, F: Fn(&str) -> Result, { let mut source = String::new(); reader.read_to_string(&mut source)?; Self::from_string(type_, source, f) } pub fn from_file(type_: Type, path: P, f: F) -> Result where P: AsRef, F: Fn(&str) -> Result, { let mut file = File::open(&path)?; match Self::from_reader(type_, &mut file, f) { Ok(v) => Ok(v), Err(Error::ShaderCompile { code, error }) => Err(Error::ShaderCompile { code: format!("{}\n{}", path.as_ref().display(), code), error, }), Err(err) => Err(err), } } pub fn id(&self) -> gl::GLuint { self.id } } impl Drop for Shader { fn drop(&mut self) { gl::delete_shader(self.id); } } /* Builder */ #[derive(Default)] pub struct Builder { shaders: Vec, transform_feedback_varyings: Option<(TransformFeedbackVaryingsMode, &'static [&'static str])>, } impl Builder { pub fn add_shader(mut self, shader: Shader) -> Self { self.shaders.push(shader); self } pub fn set_transform_feedback_varyings( mut self, mode: TransformFeedbackVaryingsMode, value: &'static [&'static str], ) -> Self { self.transform_feedback_varyings = Some((mode, value)); self } pub fn build(self) -> Result { let id = gl::create_program(); let id = Error::err_if(&0, id)?; for shader in &self.shaders { gl::attach_shader(id, shader.id()); } if let Some((mode, tfv)) = self.transform_feedback_varyings { let tfv = tfv .iter() .map(|v: &&str| CString::new(*v)) .collect::, _>>()?; let tfv = tfv .iter() .map(|v: &CString| v.as_ptr() as *const gl::GLchar) .collect::>(); let ptr: *const *const gl::GLchar = tfv.as_slice().as_ptr(); Error::checked(|| { gl::transform_feedback_varyings(id, tfv.len() as _, ptr, mode.as_enum()) })?; } gl::link_program(id); for shader in &self.shaders { gl::detach_shader(id, shader.id()); } let mut success = 1; gl::get_program_iv(id, gl::LINK_STATUS, &mut success); if success != 1 { let mut len = 0; gl::get_program_iv(id, gl::INFO_LOG_LENGTH, &mut len); let mut buffer = Vec::::with_capacity(len as usize + 1); buffer.resize(len as usize, 0); gl::get_program_info_log( id, len, null_mut(), buffer.as_mut_ptr() as *mut gl::types::GLchar, ); let msg = from_utf8(&buffer).map_err(Error::Utf8Error)?; return Err(Error::ShaderLink(msg.into())); } Ok(Program { id }) } } /* Type */ #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Type { Vertex, Compute, Fragment, Geometry, TesslationControl, TesslationEvaluation, } impl AsEnum for Type { fn as_enum(&self) -> gl::GLenum { match self { Self::Vertex => gl::VERTEX_SHADER, Self::Compute => gl::COMPUTE_SHADER, Self::Fragment => gl::FRAGMENT_SHADER, Self::Geometry => gl::GEOMETRY_SHADER, Self::TesslationControl => gl::TESS_CONTROL_SHADER, Self::TesslationEvaluation => gl::TESS_EVALUATION_SHADER, } } } /* TransformFeedbackVaryingsMode */ pub enum TransformFeedbackVaryingsMode { Interleaved, Separate, } impl AsEnum for TransformFeedbackVaryingsMode { fn as_enum(&self) -> gl::GLenum { match self { Self::Interleaved => gl::INTERLEAVED_ATTRIBS, Self::Separate => gl::SEPARATE_ATTRIBS, } } } /* Uniform */ pub enum Uniform<'a> { Matrix4f(&'a Matrix4f), Vector4f(&'a Vector4f), Vector2f(&'a Vector2f), Float(f32), Texture(gl::GLint), TextureVec(&'a [gl::GLint]), } /* IntoUniformLocation */ pub trait IntoUniformLocation { fn into_location(self, program: &Program) -> Result; } impl IntoUniformLocation for gl::GLint { fn into_location(self, _program: &Program) -> Result { Ok(self) } } impl IntoUniformLocation for &str { fn into_location(self, program: &Program) -> Result { program.uniform_location(self) } } impl IntoUniformLocation for String { fn into_location(self, program: &Program) -> Result { program.uniform_location(self) } } /* IntoUniformBlockIndex */ pub trait IntoUniformBlockIndex { fn into_index(self, program: &Program) -> Result; } impl IntoUniformBlockIndex for gl::GLuint { fn into_index(self, _program: &Program) -> Result { Ok(self) } } impl IntoUniformBlockIndex for &str { fn into_index(self, program: &Program) -> Result { program.uniform_block_index(self) } } impl IntoUniformBlockIndex for String { fn into_index(self, program: &Program) -> Result { program.uniform_block_index(self) } }