use crate::{Error, ResolvedCrop};
use std::ffi::CString;
unsafe fn compile_shader(kind: u32, src: &str) -> crate::Result<u32> {
let shader = edgefirst_gl::gl::CreateShader(kind);
let c = CString::new(src).map_err(|e| Error::OpenGl(format!("shader CString: {e}")))?;
let ptr = c.as_ptr();
let len = src.len() as i32;
edgefirst_gl::gl::ShaderSource(shader, 1, &ptr, &len);
edgefirst_gl::gl::CompileShader(shader);
let mut ok = 0i32;
edgefirst_gl::gl::GetShaderiv(shader, edgefirst_gl::gl::COMPILE_STATUS, &mut ok);
if ok == 0 {
let mut log = [0u8; 4096];
let mut log_len = 0i32;
edgefirst_gl::gl::GetShaderInfoLog(
shader,
log.len() as i32,
&mut log_len,
log.as_mut_ptr() as *mut _,
);
let msg = String::from_utf8_lossy(&log[..log_len.max(0) as usize]).into_owned();
edgefirst_gl::gl::DeleteShader(shader);
return Err(Error::OpenGl(format!(
"shader compile failed (kind=0x{kind:x}): {msg}"
)));
}
Ok(shader)
}
pub(super) unsafe fn compile_program(vertex_src: &str, fragment_src: &str) -> crate::Result<u32> {
let vs = compile_shader(edgefirst_gl::gl::VERTEX_SHADER, vertex_src)?;
struct ProgramBuild {
vs: Option<u32>,
fs: Option<u32>,
program: Option<u32>,
}
impl Drop for ProgramBuild {
fn drop(&mut self) {
unsafe {
if let Some(p) = self.program {
edgefirst_gl::gl::DeleteProgram(p);
}
if let Some(s) = self.fs {
edgefirst_gl::gl::DeleteShader(s);
}
if let Some(s) = self.vs {
edgefirst_gl::gl::DeleteShader(s);
}
}
}
}
let mut state = ProgramBuild {
vs: Some(vs),
fs: None,
program: None,
};
let fs = compile_shader(edgefirst_gl::gl::FRAGMENT_SHADER, fragment_src)?;
state.fs = Some(fs);
let program = edgefirst_gl::gl::CreateProgram();
state.program = Some(program);
edgefirst_gl::gl::AttachShader(program, vs);
edgefirst_gl::gl::AttachShader(program, fs);
edgefirst_gl::gl::LinkProgram(program);
let mut ok = 0i32;
edgefirst_gl::gl::GetProgramiv(program, edgefirst_gl::gl::LINK_STATUS, &mut ok);
if ok == 0 {
let mut log = [0u8; 4096];
let mut log_len = 0i32;
edgefirst_gl::gl::GetProgramInfoLog(
program,
log.len() as i32,
&mut log_len,
log.as_mut_ptr() as *mut _,
);
let msg = String::from_utf8_lossy(&log[..log_len.max(0) as usize]).into_owned();
return Err(Error::OpenGl(format!("program link failed: {msg}")));
}
edgefirst_gl::gl::DeleteShader(state.vs.take().unwrap());
edgefirst_gl::gl::DeleteShader(state.fs.take().unwrap());
let program = state.program.take().unwrap();
std::mem::forget(state);
Ok(program)
}
pub(super) unsafe fn set_tex_filter(target: u32, filter: u32) {
edgefirst_gl::gl::TexParameteri(target, edgefirst_gl::gl::TEXTURE_MIN_FILTER, filter as i32);
edgefirst_gl::gl::TexParameteri(target, edgefirst_gl::gl::TEXTURE_MAG_FILTER, filter as i32);
}
pub(super) unsafe fn set_tex_filter_clamp(target: u32, filter: u32) {
set_tex_filter(target, filter);
edgefirst_gl::gl::TexParameteri(
target,
edgefirst_gl::gl::TEXTURE_WRAP_S,
edgefirst_gl::gl::CLAMP_TO_EDGE as i32,
);
edgefirst_gl::gl::TexParameteri(
target,
edgefirst_gl::gl::TEXTURE_WRAP_T,
edgefirst_gl::gl::CLAMP_TO_EDGE as i32,
);
}
pub(super) unsafe fn check_framebuffer_complete() -> Result<(), u32> {
let status = edgefirst_gl::gl::CheckFramebufferStatus(edgefirst_gl::gl::FRAMEBUFFER);
if status == edgefirst_gl::gl::FRAMEBUFFER_COMPLETE {
Ok(())
} else {
Err(status)
}
}
pub(super) fn float_crop_uniforms(
crop: &ResolvedCrop,
src_w: usize,
src_h: usize,
dst_w: usize,
dst_h: usize,
) -> crate::Result<([f32; 4], [f32; 4], [f32; 3])> {
crop.check_crop_dims(src_w, src_h, dst_w, dst_h)?;
let src_rect_uv = match crop.src_rect {
Some(r) => [
r.left as f32 / src_w as f32,
r.top as f32 / src_h as f32,
r.width as f32 / src_w as f32,
r.height as f32 / src_h as f32,
],
None => [0.0, 0.0, 1.0, 1.0],
};
let dst_rect_px = match crop.dst_rect {
Some(r) => [r.left as f32, r.top as f32, r.width as f32, r.height as f32],
None => [0.0, 0.0, dst_w as f32, dst_h as f32],
};
let pad_color = match crop.dst_color {
Some([r, g, b, _]) => [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0],
None => [0.0, 0.0, 0.0],
};
Ok((src_rect_uv, dst_rect_px, pad_color))
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::float_crop_uniforms;
use crate::{Rect, ResolvedCrop};
#[test]
fn identity_crop_is_full_image() {
let (src_uv, dst_px, pad) =
float_crop_uniforms(&ResolvedCrop::no_crop(), 640, 480, 320, 240).unwrap();
assert_eq!(src_uv, [0.0, 0.0, 1.0, 1.0]);
assert_eq!(dst_px, [0.0, 0.0, 320.0, 240.0]);
assert_eq!(pad, [0.0, 0.0, 0.0]);
}
#[test]
fn src_rect_normalizes_to_source_dims() {
let crop = ResolvedCrop {
src_rect: Some(Rect::new(160, 120, 320, 240)),
..ResolvedCrop::no_crop()
};
let (src_uv, _, _) = float_crop_uniforms(&crop, 640, 480, 320, 240).unwrap();
assert_eq!(src_uv, [0.25, 0.25, 0.5, 0.5]);
}
#[test]
fn dst_rect_is_pixel_coords_and_pad_color_normalizes() {
let crop = ResolvedCrop {
dst_rect: Some(Rect::new(10, 20, 100, 50)),
dst_color: Some([255, 128, 0, 255]),
..ResolvedCrop::no_crop()
};
let (_, dst_px, pad) = float_crop_uniforms(&crop, 640, 480, 320, 240).unwrap();
assert_eq!(dst_px, [10.0, 20.0, 100.0, 50.0]);
assert_eq!(pad, [1.0, 128.0 / 255.0, 0.0]);
}
#[test]
fn out_of_bounds_src_rect_errors() {
let crop = ResolvedCrop {
src_rect: Some(Rect::new(6, 0, 4, 4)), ..ResolvedCrop::no_crop()
};
let err = float_crop_uniforms(&crop, 8, 8, 8, 8).unwrap_err();
assert!(
matches!(err, crate::Error::CropInvalid(_)),
"expected CropInvalid, got {err:?}"
);
}
}