mod gl {
pub use gl::types::*;
pub use gl::*;
}
use glutin;
pub mod errors;
pub mod vertices;
pub use self::errors::Error;
use std::{ffi,mem,ptr};
use imagefmt::{Image, ColFmt};
use std::marker::PhantomData;
use std::os::raw::c_void;
/// Clear the screen to a generic color
pub fn clear() {
unsafe {
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::ClearColor(0.3, 0.3, 0.3, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
}
/// Abstract over an OpenGL context as well as a basic event loop
pub struct Window {
events: glutin::EventsLoop,
gl_window: glutin::GlWindow,
}
impl Window {
/// Attempt to create a new window, and load all the relevant
/// OpenGL symbols in the process
pub fn create() -> Result<Window, Error> {
use glutin::GlContext;
let events = glutin::EventsLoop::new();
let window = glutin::WindowBuilder::new();
let context = glutin::ContextBuilder::new();
let gl_window = glutin::GlWindow::new(window, context, &events)?;
unsafe { gl_window.make_current() }?;
gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
Ok(Window { events, gl_window })
}
/// Run the main event loop, calling the supplied callback with
/// every event
pub fn run<F>(self, mut cb: F) -> Result<(), Error>
where F: FnMut(glutin::Event) -> glutin::ControlFlow
{
use glutin::GlContext;
let Window { mut events, gl_window } = self;
let mut result = Ok(());
events.run_forever(|ev| {
let r = cb(ev);
match gl_window.swap_buffers() {
Ok(()) => r,
Err(ctx) => {
result = Err(Error::GlutinContext(ctx));
glutin::ControlFlow::Break
}
}
});
result
}
}
/// Wrap an OpenGL shader. This will automatically be cleaned up from
/// the OpenGL state machine when it is dropped.
pub struct Shader {
pub n: gl::GLuint,
}
impl Drop for Shader {
fn drop(&mut self) {
unsafe { gl::DeleteShader(self.n); }
}
}
/// Right now, we're only concerned with either Fragment or Vertex
/// shaders. We might eventually want Geometry shaders, as well.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum ShaderType {
Fragment,
Vertex,
}
impl ShaderType {
/// Convert a ShaderType value to OpenGL's enum value for the same
fn to_glenum(&self) -> gl::GLenum {
match *self {
ShaderType::Fragment => gl::FRAGMENT_SHADER,
ShaderType::Vertex => gl::VERTEX_SHADER,
}
}
}
impl Shader {
/// Compile a shade string and provide our Rust-level handle to it
pub fn compile(ty: ShaderType, src: &str) -> Result<Shader, Error> {
// this structure is gonna repeat a few times but also can't
// really get abstracted over in a clean way, so this comment
// could be consulted for the other functions, too.
let shader;
unsafe {
// we start by getting a new shader from OpenGL
shader = gl::CreateShader(ty.to_glenum());
// and also get the C string version of the source code
let c_str = ffi::CString::new(src.as_bytes())?;
// and then we compile it!
gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null());
gl::CompileShader(shader);
// this is the common part: we now ask OpenGL for the
// status of that compile, which OpenGL gives us by
// updating a pointer
let mut status = gl::FALSE as gl::GLint;
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);
// If it's not good...
if status != (gl::TRUE as gl::GLint) {
// then then need to get the length of the error
// message, which, again we fetch via pointer
let mut len = 0;
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
// create the buffer that's big enough for that
// message
let mut buf = Vec::with_capacity(len as usize);
buf.set_len((len as usize) - 1);
// and then fill it in via OpenGL's mechanisms
gl::GetShaderInfoLog(
shader,
len,
ptr::null_mut(),
buf.as_mut_ptr() as *mut gl::GLchar
);
// and finally return it!
return Err(Error::CompileError(String::from_utf8(buf)?));
}
}
Ok(Shader { n: shader })
}
}
//
/// Represents a linked set of OpenGL shaders.
pub struct Program {
pub p: gl::GLuint,
pub shaders: Vec<Shader>,
}
impl Drop for Program {
fn drop(&mut self) {
self.shaders = vec![];
unsafe {
gl::DeleteProgram(self.p)
}
}
}
impl Program {
/// Link several existing shaders into a single Program
pub fn link(shaders: Vec<Shader>) -> Result<Program, Error> {
// the stuff in here looks a lot like Shader::compile, so
// consult the inline docs for that to figure out what's going on
unsafe {
let program = gl::CreateProgram();
for s in shaders.iter() {
gl::AttachShader(program, s.n);
}
gl::LinkProgram(program);
let mut status = gl::FALSE as gl::GLint;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
// Fail on error
if status != (gl::TRUE as gl::GLint) {
let mut len: gl::GLint = 0;
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = Vec::with_capacity(len as usize);
buf.set_len((len as usize) - 1);
gl::GetProgramInfoLog(
program,
len,
ptr::null_mut(),
buf.as_mut_ptr() as *mut gl::GLchar,
);
return Err(Error::LinkError(String::from_utf8(buf)?));
}
Ok(Program {
p: program,
shaders: shaders,
})
}
}
pub fn use_program(&self) {
unsafe { gl::UseProgram(self.p); }
}
pub fn bind_frag_data_location(&self, idx: u32, s: &str) -> Result<(), Error> {
unsafe {
gl::BindFragDataLocation(
self.p,
idx,
ffi::CString::new(s)?.as_ptr(),
);
}
Ok(())
}
pub fn get_attrib_location(&self, s: &str) -> Result<i32, Error> {
Ok(unsafe {
gl::GetAttribLocation(
self.p,
ffi::CString::new(s)?.as_ptr(),
)
})
}
pub fn set_texture(
&self,
s: &str,
tex: &Texture,
n: u32,
) -> Result<(), Error> {
unsafe {
gl::ActiveTexture(if n < gl::MAX_COMBINED_TEXTURE_IMAGE_UNITS {
gl::TEXTURE0 + n
} else {
panic!("Texture unit {} too high", n)
});
gl::BindTexture(gl::TEXTURE_2D, tex.idx);
let t = gl::GetUniformLocation(
self.p,
ffi::CString::new(s)?.as_ptr(),
);
gl::Uniform1i(t, n as i32);
}
Ok(())
}
}
//
pub struct VertexArray<'p> {
idx: u32,
program: &'p Program,
}
impl<'p> Drop for VertexArray<'p> {
fn drop(&mut self) {
unsafe { gl::DeleteVertexArrays(1, &self.idx); }
}
}
pub struct VertexBuffer<'p, Vtx> {
idx: u32,
data: VertexArray<'p>,
phantom: PhantomData<Vtx>,
len: usize,
}
pub struct VBORef<'a, Vtx : 'a> {
rf: &'a VertexBuffer<'a, Vtx>,
}
impl<'p, Vtx> Drop for VertexBuffer<'p, Vtx> {
fn drop(&mut self) {
unsafe { gl::DeleteBuffers(1, &self.idx); }
}
}
impl<'p> VertexArray<'p> {
pub fn new(program: &'p Program) -> VertexArray<'p> {
let mut idx = 0;
unsafe {
gl::GenVertexArrays(1, &mut idx);
gl::BindVertexArray(idx);
}
VertexArray { idx, program }
}
pub fn bind(&self) {
unsafe {
gl::BindVertexArray(self.idx);
}
}
pub fn unbind(&self) {
unsafe {
gl::BindVertexArray(0);
}
}
}
impl<'p, Vtx: vertices::Vertex> VertexBuffer<'p, Vtx> {
pub fn new_array_buffer(
data: VertexArray<'p>,
vertex_data: &[Vtx],
) -> VertexBuffer<'p, Vtx> {
let mut idx = 0;
unsafe {
gl::GenBuffers(1, &mut idx);
gl::BindBuffer(gl::ARRAY_BUFFER, idx);
gl::BufferData(
gl::ARRAY_BUFFER,
(vertex_data.len() * mem::size_of::<Vtx>()) as gl::GLsizeiptr,
mem::transmute(&vertex_data[0]),
gl::STATIC_DRAW,
);
}
let phantom = PhantomData;
let len = vertex_data.len();
VertexBuffer { idx, data, phantom, len }
}
pub fn with<F, T>(&self, f: F) -> T
where F: for<'a> Fn(VBORef<'a, Vtx>) -> T
{
unsafe {
self.data.bind();
gl::BindBuffer(gl::ARRAY_BUFFER, self.idx);
}
let res = f(VBORef { rf: &self });
unsafe {
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
self.data.unbind();
}
res
}
}
impl<'p, Vtx: vertices::Vertex> VBORef<'p, Vtx> {
pub fn draw(&self) {
unsafe {
gl::DrawArrays(gl::TRIANGLES, 0, self.rf.len as i32);
}
}
pub fn bind_position(&self, name: &str) -> Result<(), Error> {
let attr = self.rf.data.program.get_attrib_location(name)?;
let (offset, num) = Vtx::pos_location();
unsafe {
let off = ptr::null::<u8>().offset(
(mem::size_of::<gl::GLfloat>() * offset as usize) as isize);
gl::EnableVertexAttribArray(attr as gl::GLuint);
gl::VertexAttribPointer(
attr as gl::GLuint,
num,
gl::FLOAT,
gl::FALSE as gl::GLboolean,
Vtx::size() as i32,
off as *const c_void,
)
}
Ok(())
}
pub fn bind_uv(&self, name: &str) -> Result<(), Error> {
let attr = self.rf.data.program.get_attrib_location(name)?;
let (offset, num) = match Vtx::uv_location() {
Some(p) => p,
None => return Err(Error::OtherError(
"No UV data for associated vertex".to_owned())),
};
unsafe {
let off = ptr::null::<u8>().offset(
(mem::size_of::<gl::GLfloat>() * offset as usize) as isize);
gl::EnableVertexAttribArray(attr as gl::GLuint);
gl::VertexAttribPointer(
attr as gl::GLuint,
num,
gl::FLOAT,
gl::FALSE as gl::GLboolean,
Vtx::size() as i32,
off as *const c_void,
)
}
Ok(())
}
}
#[allow(dead_code)]
pub struct Texture {
idx: u32,
image: Image<u8>,
}
impl Texture {
/// Take a raw Bitmap value and create an OpenGL texture from it
pub fn new_from_bitmap(image: Image<u8>) -> Texture {
let mut idx = 0;
unsafe {
gl::GenTextures(1, &mut idx);
gl::BindTexture(gl::TEXTURE_2D, idx);
let params = [
(gl::TEXTURE_MIN_FILTER, gl::NEAREST as gl::GLint),
(gl::TEXTURE_MAG_FILTER, gl::NEAREST as gl::GLint),
(gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint),
(gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint),
];
for &(param, value) in params.into_iter() {
gl::TexParameteri(gl::TEXTURE_2D, param, value)
}
let fmt = match image.fmt {
ColFmt::RGB => gl::RGB,
ColFmt::RGBA => gl::RGBA,
_ => panic!("Unknown format: {:?}", image.fmt),
};
gl::TexImage2D(
gl::TEXTURE_2D,
0,
fmt as gl::GLint,
image.w as gl::GLint,
image.h as gl::GLint,
0,
fmt,
gl::UNSIGNED_BYTE,
mem::transmute(&image.buf[0]),
);
gl::BindTexture(gl::TEXTURE_2D, 0);
}
Texture { idx, image }
}
pub fn new_from_png(p: &str) -> Result<Texture, Error> {
use imagefmt;
let mut f = ::std::fs::File::open(p)?;
let bitmap = imagefmt::png::read(&mut f, imagefmt::ColFmt::RGBA)?;
Ok(Texture::new_from_bitmap(bitmap))
}
}