use std::ops::Deref;
use anyhow::anyhow;
use anyhow::Context as _;
use anyhow::Result;
use crate::sys;
use crate::sys::Gl as _;
#[derive(Debug)]
pub struct Shader {
context: sys::Context,
shader: sys::Shader,
}
impl Shader {
pub fn glsl_version() -> String {
let (major, minor, suffix) = sys::version();
let version = format!(
"{major}{minor}0{}",
suffix
.map(|suffix| format!(" {suffix}"))
.unwrap_or_default()
);
assert!(
(major, minor) >= (3, if suffix == Some("es") { 0 } else { 3 }),
"unsupported OpenGL version: {version:?}"
);
version
}
pub fn new(
shader_type: sys::ShaderType,
shader_file: &str,
context: &sys::Context,
) -> Result<Self> {
let shader = context
.create_shader(shader_type)
.context("failed to create shader object")?;
let slf = Self {
context: context.clone(),
shader,
};
let () = context.set_shader_source(&slf.shader, shader_file);
let () = context.compile_shader(&slf.shader).map_err(|log| {
let log = String::from_utf8_lossy(log.as_slice());
anyhow!("failed to compile {} shader: {log}", shader_type.as_str())
})?;
Ok(slf)
}
}
impl Deref for Shader {
type Target = sys::Shader;
#[inline]
fn deref(&self) -> &Self::Target {
&self.shader
}
}
impl Drop for Shader {
#[inline]
fn drop(&mut self) {
let () = self.context.delete_shader(&self.shader);
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_fork::fork;
use crate::winit::with_opengl_context;
#[fork]
#[test]
fn vertex_shader_creation() {
let shader_code = format!(
r#"#version {glsl_version} core
in vec4 position;
void main() {{
gl_Position = position;
}}
"#,
glsl_version = Shader::glsl_version()
);
with_opengl_context(|| {
let gl_context = sys::Context::default();
let _shader = Shader::new(sys::ShaderType::Vertex, &shader_code, &gl_context).unwrap();
})
}
#[fork]
#[test]
fn fragment_shader_creation() {
let shader_code = format!(
r#"#version {glsl_version} core
out vec4 color;
void main() {{
color = vec4(1.0f, 1.0f, 1.0f, 1.0f);
}}
"#,
glsl_version = Shader::glsl_version()
);
with_opengl_context(|| {
let gl_context = sys::Context::default();
let _shader = Shader::new(sys::ShaderType::Fragment, &shader_code, &gl_context).unwrap();
})
}
#[fork]
#[test]
fn shader_creation_failure() {
let shader_code = r#"
not a valid identifier here
"#;
with_opengl_context(|| {
let gl_context = sys::Context::default();
let err = Shader::new(sys::ShaderType::Vertex, shader_code, &gl_context).unwrap_err();
assert!(err.to_string().contains("syntax error"), "{err:#}");
})
}
}