use self::{
batches::{Batch, DrawCall},
camera::Camera,
mesh::{Mesh, MeshBuilder, Vertex},
shaders::Program,
shaders::{Shader, ShaderType},
sprites::Sprite,
text::{Font, FontError, TextSettings},
textures::Texture,
};
use gl;
use maths::{Vector2f, Vector2u, Vector3f};
use sdl2;
use std::{error, fmt, ptr};
use transform::Transform;
mod batches;
pub mod camera;
pub mod mesh;
pub mod shaders;
pub mod sprites;
pub mod text;
pub mod textures;
#[derive(Debug)]
pub enum DrawingError {
SdlError(String),
GlError(String),
MeshEBONotInitialized,
MeshVAONotInitialized,
ShaderError(shaders::ShaderError),
WindowBuildError(sdl2::video::WindowBuildError),
}
impl fmt::Display for DrawingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DrawingError::SdlError(string) => write!(f, "{}", string),
DrawingError::GlError(string) => write!(f, "{}", string),
DrawingError::ShaderError(error) => write!(f, "{}", error),
DrawingError::WindowBuildError(error) => write!(f, "{}", error),
DrawingError::MeshEBONotInitialized => write!(f, "Mesh EBO not initialized"),
DrawingError::MeshVAONotInitialized => write!(f, "Mesh VAO not initialized"),
}
}
}
impl error::Error for DrawingError {
fn cause(&self) -> Option<&error::Error> {
match self {
DrawingError::ShaderError(error) => Some(error),
DrawingError::WindowBuildError(error) => Some(error),
_ => None,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct WindowSettings<'a> {
pub title: &'a str,
pub width: u32,
pub height: u32,
pub vsync: bool,
}
pub struct GraphicsManager {
window: sdl2::video::Window,
#[allow(dead_code)]
gl_context: sdl2::video::GLContext,
program: Program,
quad: Mesh,
batches: Vec<Batch>,
}
impl GraphicsManager {
pub fn new(sdl: &sdl2::Sdl, window_settings: WindowSettings) -> Result<Self, DrawingError> {
let video = sdl.video().map_err(DrawingError::SdlError)?;
{
let gl_attr = video.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(3, 3);
}
let window = video
.window(
window_settings.title,
window_settings.width,
window_settings.height,
).opengl()
.resizable()
.build()
.map_err(DrawingError::WindowBuildError)?;
let gl_context = window.gl_create_context().map_err(DrawingError::GlError)?;
gl::load_with(|s| video.gl_get_proc_address(s) as *const gl::types::GLvoid);
video.gl_set_swap_interval(if window_settings.vsync {
sdl2::video::SwapInterval::VSync
} else {
sdl2::video::SwapInterval::Immediate
});
unsafe {
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::ClearColor(0.3, 0.3, 0.5, 1.0);
}
let vertex_shader =
Shader::from_source(include_str!("shaders/standard.vert"), ShaderType::Vertex)
.map_err(DrawingError::ShaderError)?;
let fragment_shader =
Shader::from_source(include_str!("shaders/standard.frag"), ShaderType::Fragment)
.map_err(DrawingError::ShaderError)?;
let program = Program::from_shaders(vertex_shader, fragment_shader)
.map_err(DrawingError::ShaderError)?;
let quad = MeshBuilder {
vertices: vec![
Vertex {
position: Vector3f::new(0.5, 0.5, 0.0),
uv: Vector2f::new(1.0, 0.0),
},
Vertex {
position: Vector3f::new(0.5, -0.5, 0.0),
uv: Vector2f::new(1.0, 1.0),
},
Vertex {
position: Vector3f::new(-0.5, -0.5, 0.0),
uv: Vector2f::new(0.0, 1.0),
},
Vertex {
position: Vector3f::new(-0.5, 0.5, 0.0),
uv: Vector2f::new(0.0, 0.0),
},
],
indices: vec![0, 1, 2, 0, 2, 3],
}.build();
Ok(Self {
window,
gl_context,
program,
quad,
batches: Vec::new(),
})
}
pub fn window_size(&self) -> Vector2u {
self.window.size().into()
}
pub fn resize(&mut self, width: i32, height: i32) {
unsafe {
gl::Viewport(0, 0, width as gl::types::GLint, height as gl::types::GLint);
}
}
pub fn draw_sprite(&mut self, sprite: &Sprite, transform: &Transform, camera: &Camera) {
let drawcall = DrawCall {
program: self.program,
mesh: self.quad,
texture: sprite.texture(),
tex_position: sprite.gl_position(),
matrix: camera.matrix(self.window.size().into()) * transform.matrix(),
};
self.queue_drawcall(&drawcall);
}
pub fn draw_text(
&mut self,
text: &str,
font: &mut Font,
settings: TextSettings,
transform: &Transform,
camera: &Camera,
) -> Result<(), FontError> {
for char_position in font.get_glyphs(text, settings)? {
let texture = font.texture();
let char_transform = Transform {
position: transform.position + Vector3f::new(
char_position.world_position.x,
char_position.world_position.y,
0.0,
),
scale: Vector3f::new(
transform.scale.x * char_position.world_position.z,
transform.scale.y * char_position.world_position.w,
transform.scale.z,
),
rotation: transform.rotation,
};
let drawcall = DrawCall {
program: self.program,
mesh: self.quad,
texture,
tex_position: char_position.texture_position,
matrix: camera.matrix(self.window.size().into()) * char_transform.matrix(),
};
self.queue_drawcall(&drawcall);
}
Ok(())
}
pub fn queue_drawcall(&mut self, drawcall: &DrawCall) {
for batch in &mut self.batches {
if batch.add(drawcall) {
return;
}
}
self.batches.push(Batch::new(drawcall));
}
pub fn render(&mut self) -> Result<(), DrawingError> {
unsafe {
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
}
for batch in &self.batches {
self.draw(batch)?
}
self.batches.clear();
self.window.gl_swap_window();
Ok(())
}
fn draw(&self, batch: &Batch) -> Result<(), DrawingError> {
batch.mesh().check()?;
batch.program().set_used();
unsafe {
gl::BindTexture(gl::TEXTURE_2D, batch.texture());
gl::BindVertexArray(batch.mesh().vao());
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, batch.mesh().ebo());
}
batch.buffer_data();
unsafe {
gl::DrawElementsInstanced(
gl::TRIANGLES, batch.mesh().indices_count() as i32, gl::UNSIGNED_INT, ptr::null(), batch.obj_count() as gl::types::GLint, );
}
Ok(())
}
}