use std::ffi::CStr; use std::ptr::null_mut; use safer_ffi::layout::ReprC; use crate::data_types::{Matrix, Vec3}; use crate::CurrentContextWithFns; #[derive(Debug)] pub struct Shader { shader: crate::sys::types::GLuint, } impl Shader { #[must_use] pub fn new(current_context: &CurrentContextWithFns<'_>, kind: Kind) -> Self { let shader = unsafe { current_context .fns() .CreateShader(kind as crate::sys::types::GLenum) }; Self { shader } } /// Sets the source code of this shader. /// /// # Errors /// Returns `Err` if `source` is not ASCII. pub fn set_source( &self, current_context: &CurrentContextWithFns<'_>, source: &str, ) -> Result<(), Error> { if !source.is_ascii() { return Err(Error::SourceNotAscii); } let length: crate::sys::types::GLint = source.len().try_into().map_err(|_| Error::SourceTooLarge { length: source.len(), max_length: crate::sys::types::GLint::MAX as usize, })?; unsafe { current_context.fns().ShaderSource( self.shader, 1, &source.as_ptr().cast(), &raw const length, ); } Ok(()) } /// Compiles this shader. /// /// # Errors /// Returns `Err` if compiling fails. pub fn compile( &self, current_context: &CurrentContextWithFns<'_>, ) -> Result<(), Error> { unsafe { current_context.fns().CompileShader(self.shader); } let mut compile_success = crate::sys::types::GLint::default(); unsafe { current_context.fns().GetShaderiv( self.shader, crate::sys::COMPILE_STATUS, &raw mut compile_success, ); } if compile_success == 0 { let info_log = self.get_info_log(current_context); return Err(Error::CompileFailed { log: info_log }); } Ok(()) } pub fn delete(self, current_context: &CurrentContextWithFns<'_>) { unsafe { current_context.fns().DeleteShader(self.shader); } } fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String { const BUF_SIZE: crate::sys::types::GLsizei = 512; let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize]; unsafe { current_context.fns().GetShaderInfoLog( self.shader, BUF_SIZE, 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()) } } } /// Shader kind. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] pub enum Kind { Vertex = crate::sys::VERTEX_SHADER, Fragment = crate::sys::FRAGMENT_SHADER, } /// Shader program #[derive(Debug, PartialEq, Eq, Hash)] pub struct Program { program: crate::sys::types::GLuint, } impl Program { #[must_use] pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self { let program = unsafe { current_context.fns().CreateProgram() }; Self { program } } pub fn attach(&self, current_context: &CurrentContextWithFns<'_>, shader: &Shader) { unsafe { current_context .fns() .AttachShader(self.program, shader.shader); } } /// Links this program. /// /// # Errors /// Returns `Err` if linking fails. pub fn link(&self, current_context: &CurrentContextWithFns<'_>) -> Result<(), Error> { unsafe { current_context.fns().LinkProgram(self.program); } let mut link_success = crate::sys::types::GLint::default(); unsafe { current_context.fns().GetProgramiv( self.program, crate::sys::LINK_STATUS, &raw mut link_success, ); } if link_success == 0 { let info_log = self.get_info_log(current_context); return Err(Error::LinkFailed { log: info_log }); } Ok(()) } pub fn activate(&self, current_context: &CurrentContextWithFns<'_>) { unsafe { current_context.fns().UseProgram(self.program); } } pub fn set_uniform( &self, current_context: &CurrentContextWithFns<'_>, name: &CStr, var: &impl UniformVariable, ) { let location = UniformLocation(unsafe { current_context .fns() .GetUniformLocation(self.program, name.as_ptr().cast()) }); var.set(current_context, self, location); } pub fn delete(self, current_context: &CurrentContextWithFns<'_>) { unsafe { current_context.fns().DeleteProgram(self.program); } } fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String { const BUF_SIZE: crate::sys::types::GLsizei = 512; let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize]; unsafe { current_context.fns().GetProgramInfoLog( self.program, BUF_SIZE, 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()) } } } pub trait UniformVariable: ReprC + sealed::Sealed { fn set( &self, current_context: &CurrentContextWithFns<'_>, program: &Program, uniform_location: UniformLocation, ); } impl UniformVariable for f32 { fn set( &self, current_context: &CurrentContextWithFns<'_>, program: &Program, uniform_location: UniformLocation, ) { unsafe { current_context.fns().ProgramUniform1f( program.program, uniform_location.0, *self, ); } } } impl sealed::Sealed for f32 {} impl UniformVariable for i32 { fn set( &self, current_context: &CurrentContextWithFns<'_>, program: &Program, uniform_location: UniformLocation, ) { unsafe { current_context.fns().ProgramUniform1i( program.program, uniform_location.0, *self, ); } } } impl sealed::Sealed for i32 {} impl UniformVariable for Vec3 { fn set( &self, current_context: &CurrentContextWithFns<'_>, program: &Program, uniform_location: UniformLocation, ) { unsafe { current_context.fns().ProgramUniform3f( program.program, uniform_location.0, self.x, self.y, self.z, ); } } } impl sealed::Sealed for Vec3 {} impl UniformVariable for Matrix { fn set( &self, current_context: &CurrentContextWithFns<'_>, program: &Program, uniform_location: UniformLocation, ) { unsafe { current_context.fns().ProgramUniformMatrix4fv( program.program, uniform_location.0, 1, crate::sys::FALSE, self.items.as_ptr().cast::(), ); } } } impl sealed::Sealed for Matrix {} #[derive(Debug)] pub struct UniformLocation(crate::sys::types::GLint); /// Shader error. #[derive(Debug, thiserror::Error)] pub enum Error { #[error("All characters in source are not within the ASCII range")] SourceNotAscii, #[error("Source is too large. Length ({length}) must be < {max_length}")] SourceTooLarge { length: usize, max_length: usize }, #[error("Failed to compile shader")] CompileFailed { log: String }, #[error("Failed to link shader program")] LinkFailed { log: String }, } mod sealed { pub trait Sealed {} }