use std::ffi::CStr; use std::ptr::null_mut; #[derive(Debug)] pub struct Shader { shader: gl::types::GLuint, } impl Shader { pub fn new(kind: Kind) -> Self { let shader = unsafe { gl::CreateShader(kind.into_gl()) }; Self { shader } } pub fn set_source(&self, source: &str) -> Result<(), Error> { if !source.is_ascii() { return Err(Error::SourceNotAscii); } unsafe { #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] gl::ShaderSource( self.shader, 1, &source.as_ptr().cast(), &(source.len() as gl::types::GLint), ); } Ok(()) } pub fn compile(&self) -> Result<(), Error> { unsafe { gl::CompileShader(self.shader); } let mut compile_success = gl::types::GLint::default(); unsafe { gl::GetShaderiv(self.shader, gl::COMPILE_STATUS, &mut compile_success); } if compile_success == 0 { let info_log = self.get_info_log(); return Err(Error::CompileFailed(info_log)); } Ok(()) } fn get_info_log(&self) -> String { let mut buf = vec![gl::types::GLchar::default(); 512]; unsafe { #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] gl::GetShaderInfoLog( self.shader, buf.len() as gl::types::GLsizei, null_mut(), buf.as_mut_ptr(), ); } let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } } } impl Drop for Shader { fn drop(&mut self) { unsafe { gl::DeleteShader(self.shader); } } } /// Shader kind. #[derive(Debug, Clone, Copy)] pub enum Kind { Vertex, Fragment, } impl Kind { fn into_gl(self) -> gl::types::GLenum { match self { Self::Vertex => gl::VERTEX_SHADER, Self::Fragment => gl::FRAGMENT_SHADER, } } } /// Shader program #[derive(Debug)] pub struct Program { program: gl::types::GLuint, } impl Program { pub fn new() -> Self { let program = unsafe { gl::CreateProgram() }; Self { program } } pub fn attach(&self, shader: &Shader) { unsafe { gl::AttachShader(self.program, shader.shader); } } pub fn link(&self) -> Result<(), Error> { unsafe { gl::LinkProgram(self.program); } let mut link_success = gl::types::GLint::default(); unsafe { gl::GetProgramiv(self.program, gl::LINK_STATUS, &mut link_success); } if link_success == 0 { let info_log = self.get_info_log(); return Err(Error::CompileFailed(info_log)); } Ok(()) } pub fn activate(&self) { unsafe { gl::UseProgram(self.program); } } fn get_info_log(&self) -> String { let mut buf = vec![gl::types::GLchar::default(); 512]; unsafe { #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] gl::GetProgramInfoLog( self.program, buf.len() as gl::types::GLsizei, null_mut(), buf.as_mut_ptr(), ); } let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) }; unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) } } } impl Drop for Program { fn drop(&mut self) { unsafe { gl::DeleteProgram(self.program); } } } /// Shader error. #[derive(Debug, thiserror::Error)] pub enum Error { #[error("All characters in source are not within the ASCII range")] SourceNotAscii, #[error("Failed to compile: {0}")] CompileFailed(String), }