extern crate alloc;
use alloc::ffi::CString;
use core::ptr;
use gl::types::{GLchar, GLint};
#[expect(
clippy::module_name_repetitions,
reason = "ShaderType is more descriptive; and may be used without absolute path."
)]
#[repr(u32)]
#[non_exhaustive]
pub enum ShaderType {
Vertex = gl::VERTEX_SHADER,
Fragment = gl::FRAGMENT_SHADER,
}
#[non_exhaustive]
pub struct Shader {
pub gl_id: u32,
}
impl Shader {
#[expect(
clippy::uninit_vec,
reason = "I can't find a way to fix this lint, please make a PR/issue if you know the solution"
)]
#[inline]
pub fn load_str(shader_type: ShaderType, source: &str) -> Result<Shader, String> {
#[expect(
clippy::as_conversions,
reason = "no other way to get integral value of enum variant"
)]
let gl_id = unsafe { gl::CreateShader(shader_type as u32) };
let c_source = CString::new(source.as_bytes())
.map_err(|err| format!("Realms: failed to create CString from shader source: {err}"))?;
unsafe {
gl::ShaderSource(gl_id, 1, &c_source.as_ptr(), ptr::null());
}
unsafe {
gl::CompileShader(gl_id);
}
let mut success = GLint::from(gl::FALSE);
let mut info_log: Vec<u8> = Vec::with_capacity(1024);
unsafe {
info_log.set_len(1024 - 1);
} unsafe {
gl::GetShaderiv(gl_id, gl::COMPILE_STATUS, &raw mut success);
}
if success != GLint::from(gl::TRUE) {
unsafe {
gl::GetShaderInfoLog(
gl_id,
1024,
ptr::null_mut(),
info_log.as_mut_ptr().cast::<GLchar>(),
);
};
let gl_error = String::from_utf8_lossy(&info_log);
let gl_error = gl_error
.split_once('\0')
.ok_or("Realms: received malformed shader compile error info from opengl")?
.0;
return Err(format!("Realms: failed to compile shader: {gl_error}"));
}
Ok(Shader { gl_id })
}
}
#[expect(
clippy::module_name_repetitions,
reason = "ShaderProgram is more descriptive; and may be used without absolute path."
)]
#[non_exhaustive]
pub struct ShaderProgram {
pub gl_id: u32,
}
impl ShaderProgram {
pub const NONE: ShaderProgram = ShaderProgram { gl_id: 0 };
#[expect(
clippy::needless_pass_by_value,
reason = "the caller shouldn't be able to mutate the shaders after building the shader program"
)]
#[inline]
pub fn new(shaders: Vec<Shader>) -> Result<ShaderProgram, String> {
#[expect(
clippy::multiple_unsafe_ops_per_block,
reason = "will fix when I have time; feel free to open a PR"
)]
#[expect(
clippy::uninit_vec,
reason = "we should add MaybeUninit wrapper to info_log in the future"
)]
let gl_id = unsafe {
let gl_id = gl::CreateProgram();
for shader in &shaders {
gl::AttachShader(gl_id, shader.gl_id);
}
gl::LinkProgram(gl_id);
let mut success = GLint::from(gl::FALSE);
let mut info_log = Vec::with_capacity(1024);
info_log.set_len(1024 - 1); gl::GetProgramiv(gl_id, gl::LINK_STATUS, &raw mut success);
if success != GLint::from(gl::TRUE) {
#[expect(clippy::as_conversions, reason = "need to cast mut ptr -> *mut GLchar")]
#[expect(clippy::ptr_as_ptr, reason = "need to cast mut ptr -> *mut GLchar")]
gl::GetProgramInfoLog(
gl_id,
1024,
ptr::null_mut(),
info_log.as_mut_ptr() as *mut GLchar,
);
#[expect(
clippy::absolute_paths,
reason = "importing the `str` module would clash with the `str` type"
)]
return Err(format!(
"Realms: failed to link shader program: {}",
core::str::from_utf8(&info_log).map_err(|e| e.to_string())?
));
}
for shader in &shaders {
gl::DeleteShader(shader.gl_id);
}
gl_id
};
Ok(ShaderProgram { gl_id })
}
#[inline]
pub fn bind(&self) {
unsafe {
gl::UseProgram(self.gl_id);
}
}
#[inline]
pub fn uniform_1f(&self, uniform_name: &str, data: f32) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform1f(location, data);
}
}
#[inline]
pub fn uniform_2f(&self, uniform_name: &str, data: (f32, f32)) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform2f(location, data.0, data.1);
}
}
#[inline]
pub fn uniform_3f(&self, uniform_name: &str, data: (f32, f32, f32)) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform3f(location, data.0, data.1, data.2);
}
}
#[inline]
pub fn uniform_4f(&self, uniform_name: &str, data: (f32, f32, f32, f32)) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform4f(location, data.0, data.1, data.2, data.3);
}
}
#[inline]
pub fn uniform_1i(&self, uniform_name: &str, data: i32) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform1i(location, data);
}
}
#[inline]
pub fn uniform_2i(&self, uniform_name: &str, data: (i32, i32)) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform2i(location, data.0, data.1);
}
}
#[inline]
pub fn uniform_3i(&self, uniform_name: &str, data: (i32, i32, i32)) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform3i(location, data.0, data.1, data.2);
}
}
#[inline]
pub fn uniform_4i(&self, uniform_name: &str, data: (i32, i32, i32, i32)) {
#[expect(
clippy::unwrap_used,
reason = "it is very rare that the library user will pass a `&str` that cannot be converted to a `CString`"
)]
let location = unsafe {
gl::GetUniformLocation(self.gl_id, CString::new(uniform_name).unwrap().as_ptr())
};
unsafe {
gl::Uniform4i(location, data.0, data.1, data.2, data.3);
}
}
}