summaryrefslogtreecommitdiff
path: root/opengl-bindings/src/shader.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2025-09-19 16:36:57 +0200
committerHampusM <hampus@hampusmat.com>2025-10-02 16:55:33 +0200
commitea1d70c8c28e3b96da6264021fa1c62e28fcd8e4 (patch)
tree62ae9b75ee84602899b51483ed26fa664df36888 /opengl-bindings/src/shader.rs
parent0008b374c7f3a9ef6b30ea31a4a8c98bce64649f (diff)
feat: add OpenGL bindings crate
Diffstat (limited to 'opengl-bindings/src/shader.rs')
-rw-r--r--opengl-bindings/src/shader.rs366
1 files changed, 366 insertions, 0 deletions
diff --git a/opengl-bindings/src/shader.rs b/opengl-bindings/src/shader.rs
new file mode 100644
index 0000000..5ed66a2
--- /dev/null
+++ b/opengl-bindings/src/shader.rs
@@ -0,0 +1,366 @@
+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<f32>
+{
+ 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<f32> {}
+
+impl UniformVariable for Matrix<f32, 4, 4>
+{
+ 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::<f32>(),
+ );
+ }
+ }
+}
+
+impl sealed::Sealed for Matrix<f32, 4, 4> {}
+
+#[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 {}
+}