pub mod animation;
pub mod color;
pub(crate) mod opengl;
pub mod scaling;
pub mod shader;
pub mod text;
pub mod texture;
pub mod ui;
pub use self::animation::Animation;
pub use self::color::Color;
pub use self::scaling::ScreenScaling;
pub use self::shader::Shader;
pub use self::text::{Font, Text};
pub use self::texture::Texture;
pub use self::ui::NineSlice;
pub use glm::Vec2;
use glm::{self, Mat3, Mat4};
use glyph_brush::{GlyphBrush, GlyphBrushBuilder};
use crate::error::Result;
use crate::graphics::opengl::{
BufferUsage, GLDevice, GLFramebuffer, GLIndexBuffer, GLVertexBuffer, TextureFormat,
};
use crate::window;
use crate::Context;
const MAX_SPRITES: usize = 2048;
const MAX_VERTICES: usize = MAX_SPRITES * 4;
const MAX_INDICES: usize = MAX_SPRITES * 6;
const VERTEX_STRIDE: usize = 8;
const INDEX_ARRAY: [u32; 6] = [0, 1, 2, 2, 3, 0];
const DEFAULT_FONT: &[u8] = include_bytes!("./resources/DejaVuSansMono.ttf");
#[derive(PartialEq)]
pub(crate) enum ActiveTexture {
Framebuffer,
FontCache,
User(Texture),
}
#[derive(PartialEq)]
pub(crate) enum ActiveShader {
Default,
User(Shader),
}
#[derive(PartialEq)]
pub(crate) enum ActiveProjection {
Internal,
Window,
}
#[derive(PartialEq)]
pub(crate) enum ActiveFramebuffer {
Backbuffer,
Window,
}
pub(crate) struct GraphicsContext {
vertex_buffer: GLVertexBuffer,
index_buffer: GLIndexBuffer,
texture: Option<ActiveTexture>,
backbuffer_texture: Texture,
font_cache_texture: Texture,
shader: ActiveShader,
default_shader: Shader,
projection: ActiveProjection,
internal_projection: Mat4,
window_projection: Mat4,
framebuffer: ActiveFramebuffer,
backbuffer: GLFramebuffer,
vertex_data: Vec<f32>,
vertex_capacity: usize,
vertex_count: usize,
element_count: usize,
internal_width: i32,
internal_height: i32,
scaling: ScreenScaling,
screen_rect: Rectangle,
font_cache: GlyphBrush<'static>,
}
impl GraphicsContext {
pub(crate) fn new(
device: &mut GLDevice,
window_width: i32,
window_height: i32,
internal_width: i32,
internal_height: i32,
scaling: ScreenScaling,
) -> Result<GraphicsContext> {
assert!(
MAX_VERTICES <= 32767,
"Can't have more than 32767 vertices to a single buffer"
);
let (backbuffer_width, backbuffer_height) = match scaling {
ScreenScaling::Resize => (window_width, window_height),
_ => (internal_width, internal_height),
};
let screen_rect =
scaling.get_screen_rect(internal_width, internal_height, window_width, window_height);
let backbuffer = device.new_framebuffer();
let backbuffer_texture = Texture::from_handle(device.new_texture(
backbuffer_width,
backbuffer_height,
TextureFormat::Rgb,
));
device.attach_texture_to_framebuffer(&backbuffer, &backbuffer_texture.handle, false);
device.set_viewport(0, 0, backbuffer_width, backbuffer_height);
let indices: Vec<u32> = INDEX_ARRAY
.iter()
.cycle()
.take(MAX_INDICES)
.enumerate()
.map(|(i, vertex)| vertex + i as u32 / 6 * 4)
.collect();
let vertex_buffer = device.new_vertex_buffer(
MAX_VERTICES * VERTEX_STRIDE,
VERTEX_STRIDE,
BufferUsage::DynamicDraw,
);
device.set_vertex_buffer_attribute(&vertex_buffer, 0, 2, 0);
device.set_vertex_buffer_attribute(&vertex_buffer, 1, 2, 2);
device.set_vertex_buffer_attribute(&vertex_buffer, 2, 4, 4);
let index_buffer = device.new_index_buffer(MAX_INDICES, BufferUsage::StaticDraw);
device.set_index_buffer_data(&index_buffer, &indices, 0);
let default_shader = Shader::from_handle(device.compile_program(
shader::DEFAULT_VERTEX_SHADER,
shader::DEFAULT_FRAGMENT_SHADER,
)?);
let font_cache = GlyphBrushBuilder::using_font_bytes(DEFAULT_FONT).build();
let (width, height) = font_cache.texture_dimensions();
let font_cache_texture = Texture::from_handle(device.new_texture(
width as i32,
height as i32,
TextureFormat::Rgba,
));
Ok(GraphicsContext {
vertex_buffer,
index_buffer,
texture: None,
backbuffer_texture,
font_cache_texture,
shader: ActiveShader::Default,
default_shader,
projection: ActiveProjection::Internal,
internal_projection: ortho(
0.0,
backbuffer_width as f32,
backbuffer_height as f32,
0.0,
-1.0,
1.0,
),
window_projection: ortho(
0.0,
window_width as f32,
window_height as f32,
0.0,
-1.0,
1.0,
),
framebuffer: ActiveFramebuffer::Backbuffer,
backbuffer,
vertex_data: Vec::with_capacity(MAX_VERTICES * VERTEX_STRIDE),
vertex_capacity: MAX_VERTICES,
vertex_count: 0,
element_count: 0,
internal_width,
internal_height,
scaling,
screen_rect,
font_cache,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Rectangle {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl Rectangle {
pub fn new(x: f32, y: f32, width: f32, height: f32) -> Rectangle {
Rectangle {
x,
y,
width,
height,
}
}
pub fn row(x: f32, y: f32, width: f32, height: f32) -> impl Iterator<Item = Rectangle> {
RectangleRow {
next_rect: Rectangle::new(x, y, width, height),
}
}
pub fn column(x: f32, y: f32, width: f32, height: f32) -> impl Iterator<Item = Rectangle> {
RectangleColumn {
next_rect: Rectangle::new(x, y, width, height),
}
}
}
#[derive(Debug, Clone)]
struct RectangleRow {
next_rect: Rectangle,
}
impl Iterator for RectangleRow {
type Item = Rectangle;
fn next(&mut self) -> Option<Rectangle> {
let current_rect = self.next_rect;
self.next_rect.x += self.next_rect.width;
Some(current_rect)
}
}
#[derive(Debug, Clone)]
struct RectangleColumn {
next_rect: Rectangle,
}
impl Iterator for RectangleColumn {
type Item = Rectangle;
fn next(&mut self) -> Option<Rectangle> {
let current_rect = self.next_rect;
self.next_rect.y += self.next_rect.height;
Some(current_rect)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DrawParams {
pub position: Vec2,
pub scale: Vec2,
pub origin: Vec2,
pub rotation: f32,
pub color: Color,
pub clip: Option<Rectangle>,
}
impl DrawParams {
pub fn new() -> DrawParams {
DrawParams::default()
}
pub fn position(mut self, position: Vec2) -> DrawParams {
self.position = position;
self
}
pub fn scale(mut self, scale: Vec2) -> DrawParams {
self.scale = scale;
self
}
pub fn origin(mut self, origin: Vec2) -> DrawParams {
self.origin = origin;
self
}
pub fn rotation(mut self, rotation: f32) -> DrawParams {
self.rotation = rotation;
self
}
pub fn color(mut self, color: Color) -> DrawParams {
self.color = color;
self
}
pub fn clip(mut self, clip: Rectangle) -> DrawParams {
self.clip = Some(clip);
self
}
#[deprecated(
since = "0.2.6",
note = "This was only intended for internal use, but was made public by mistake."
)]
#[doc(hidden)]
pub fn build_matrix(&self) -> Mat3 {
glm::translation2d(&self.position)
* glm::rotation2d(self.rotation)
* glm::scaling2d(&self.scale)
* glm::translation2d(&-self.origin)
}
}
impl Default for DrawParams {
fn default() -> DrawParams {
DrawParams {
position: Vec2::new(0.0, 0.0),
scale: Vec2::new(1.0, 1.0),
origin: Vec2::new(0.0, 0.0),
rotation: 0.0,
color: color::WHITE,
clip: None,
}
}
}
impl From<Vec2> for DrawParams {
fn from(position: Vec2) -> DrawParams {
DrawParams {
position,
..DrawParams::default()
}
}
}
pub trait Drawable {
fn draw<P>(&self, ctx: &mut Context, params: P)
where
P: Into<DrawParams>;
}
pub fn clear(ctx: &mut Context, color: Color) {
ctx.gl.clear(color.r, color.g, color.b, color.a);
}
fn push_vertex(ctx: &mut Context, x: f32, y: f32, u: f32, v: f32, color: Color) {
if ctx.graphics.vertex_count >= ctx.graphics.vertex_capacity {
flush(ctx);
}
ctx.graphics
.vertex_data
.extend_from_slice(&[x, y, u, v, color.r, color.b, color.b, color.a]);
ctx.graphics.vertex_count += 1;
}
pub(crate) fn push_quad(
ctx: &mut Context,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
mut u1: f32,
mut v1: f32,
mut u2: f32,
mut v2: f32,
params: &DrawParams,
) {
let mut fx = (x1 - params.origin.x) * params.scale.x;
let mut fy = (y1 - params.origin.y) * params.scale.y;
let mut fx2 = (x2 - params.origin.x) * params.scale.x;
let mut fy2 = (y2 - params.origin.y) * params.scale.y;
if fx2 < fx {
std::mem::swap(&mut fx, &mut fx2);
std::mem::swap(&mut u1, &mut u2);
}
if fy2 < fy {
std::mem::swap(&mut fy, &mut fy2);
std::mem::swap(&mut v1, &mut v2);
}
let (ox1, oy1, ox2, oy2, ox3, oy3, ox4, oy4) = if params.rotation != 0.0 {
let sin = params.rotation.sin();
let cos = params.rotation.cos();
(
params.position.x + (cos * fx) - (sin * fy),
params.position.y + (sin * fx) + (cos * fy),
params.position.x + (cos * fx) - (sin * fy2),
params.position.y + (sin * fx) + (cos * fy2),
params.position.x + (cos * fx2) - (sin * fy2),
params.position.y + (sin * fx2) + (cos * fy2),
params.position.x + (cos * fx2) - (sin * fy),
params.position.y + (sin * fx2) + (cos * fy),
)
} else {
(
params.position.x + fx,
params.position.y + fy,
params.position.x + fx,
params.position.y + fy2,
params.position.x + fx2,
params.position.y + fy2,
params.position.x + fx2,
params.position.y + fy,
)
};
push_vertex(ctx, ox1, oy1, u1, v1, params.color);
push_vertex(ctx, ox2, oy2, u1, v2, params.color);
push_vertex(ctx, ox3, oy3, u2, v2, params.color);
push_vertex(ctx, ox4, oy4, u2, v1, params.color);
ctx.graphics.element_count += 6;
}
pub fn draw<D: Drawable, P: Into<DrawParams>>(ctx: &mut Context, drawable: &D, params: P) {
drawable.draw(ctx, params);
}
pub fn set_texture(ctx: &mut Context, texture: &Texture) {
set_texture_ex(ctx, ActiveTexture::User(texture.clone()));
}
pub(crate) fn set_texture_ex(ctx: &mut Context, texture: ActiveTexture) {
let wrapped_texture = Some(texture);
if wrapped_texture != ctx.graphics.texture {
flush(ctx);
ctx.graphics.texture = wrapped_texture;
}
}
pub fn set_shader(ctx: &mut Context, shader: &Shader) {
set_shader_ex(ctx, ActiveShader::User(shader.clone()));
}
pub fn reset_shader(ctx: &mut Context) {
set_shader_ex(ctx, ActiveShader::Default);
}
pub(crate) fn set_shader_ex(ctx: &mut Context, shader: ActiveShader) -> Option<Shader> {
if shader != ctx.graphics.shader {
flush(ctx);
let old_shader = std::mem::replace(&mut ctx.graphics.shader, shader);
if let ActiveShader::User(s) = old_shader {
return Some(s);
}
}
None
}
pub(crate) fn set_projection_ex(ctx: &mut Context, projection: ActiveProjection) {
if projection != ctx.graphics.projection {
flush(ctx);
ctx.graphics.projection = projection;
}
}
pub(crate) fn set_framebuffer_ex(ctx: &mut Context, framebuffer: ActiveFramebuffer) {
if framebuffer != ctx.graphics.framebuffer {
flush(ctx);
ctx.graphics.framebuffer = framebuffer;
match ctx.graphics.framebuffer {
ActiveFramebuffer::Backbuffer => {
ctx.gl.bind_framebuffer(&ctx.graphics.backbuffer);
ctx.gl
.set_viewport(0, 0, get_internal_width(ctx), get_internal_height(ctx));
}
ActiveFramebuffer::Window => {
ctx.gl.bind_default_framebuffer();
ctx.gl
.set_viewport(0, 0, window::get_width(ctx), window::get_height(ctx));
}
}
}
}
pub fn flush(ctx: &mut Context) {
if !ctx.graphics.vertex_data.is_empty() {
let texture = match &ctx.graphics.texture {
None => return,
Some(ActiveTexture::Framebuffer) => &ctx.graphics.backbuffer_texture,
Some(ActiveTexture::FontCache) => &ctx.graphics.font_cache_texture,
Some(ActiveTexture::User(t)) => &t,
};
let shader = match &ctx.graphics.shader {
ActiveShader::Default => &ctx.graphics.default_shader,
ActiveShader::User(s) => &s,
};
let projection = match &ctx.graphics.projection {
ActiveProjection::Internal => &ctx.graphics.internal_projection,
ActiveProjection::Window => &ctx.graphics.window_projection,
};
ctx.gl.bind_texture(&texture.handle);
ctx.gl.bind_program(&shader.handle);
ctx.gl
.set_uniform(&shader.handle, "u_projection", &projection);
ctx.gl.bind_vertex_buffer(&ctx.graphics.vertex_buffer);
ctx.gl
.set_vertex_buffer_data(&ctx.graphics.vertex_buffer, &ctx.graphics.vertex_data, 0);
ctx.gl
.draw_elements(&ctx.graphics.index_buffer, ctx.graphics.element_count);
ctx.graphics.vertex_data.clear();
ctx.graphics.vertex_count = 0;
ctx.graphics.element_count = 0;
}
}
pub fn present(ctx: &mut Context) {
set_framebuffer_ex(ctx, ActiveFramebuffer::Window);
set_projection_ex(ctx, ActiveProjection::Window);
set_texture_ex(ctx, ActiveTexture::Framebuffer);
let user_shader = set_shader_ex(ctx, ActiveShader::Default);
clear(ctx, color::BLACK);
let screen_rect = ctx.graphics.screen_rect;
push_quad(
ctx,
screen_rect.x,
screen_rect.y,
screen_rect.x + screen_rect.width,
screen_rect.y + screen_rect.height,
0.0,
1.0,
1.0,
0.0,
&DrawParams::new(),
);
flush(ctx);
ctx.window.gl_swap_window();
set_framebuffer_ex(ctx, ActiveFramebuffer::Backbuffer);
set_projection_ex(ctx, ActiveProjection::Internal);
if let Some(s) = user_shader {
set_shader_ex(ctx, ActiveShader::User(s));
}
}
pub fn get_internal_width(ctx: &Context) -> i32 {
ctx.graphics.backbuffer_texture.width()
}
pub fn set_internal_width(ctx: &mut Context, width: i32) {
set_internal_size(ctx, width, ctx.graphics.internal_height);
}
pub fn get_internal_height(ctx: &Context) -> i32 {
ctx.graphics.backbuffer_texture.height()
}
pub fn set_internal_height(ctx: &mut Context, height: i32) {
set_internal_size(ctx, ctx.graphics.internal_width, height);
}
pub fn get_internal_size(ctx: &Context) -> (i32, i32) {
(
ctx.graphics.backbuffer_texture.width(),
ctx.graphics.backbuffer_texture.height(),
)
}
pub fn set_internal_size(ctx: &mut Context, width: i32, height: i32) {
ctx.graphics.internal_width = width;
ctx.graphics.internal_height = height;
if let ScreenScaling::Resize = ctx.graphics.scaling {
} else {
set_backbuffer_size(ctx, width, height);
update_screen_rect(ctx);
}
}
pub(crate) fn get_screen_rect(ctx: &Context) -> Rectangle {
ctx.graphics.screen_rect
}
pub fn get_scaling(ctx: &mut Context) -> ScreenScaling {
ctx.graphics.scaling
}
pub fn set_scaling(ctx: &mut Context, scaling: ScreenScaling) {
ctx.graphics.scaling = scaling;
if let ScreenScaling::Resize = ctx.graphics.scaling {
set_backbuffer_size(ctx, window::get_width(ctx), window::get_height(ctx));
} else {
set_backbuffer_size(
ctx,
ctx.graphics.internal_width,
ctx.graphics.internal_height,
);
}
update_screen_rect(ctx);
}
pub(crate) fn set_backbuffer_size(ctx: &mut Context, width: i32, height: i32) {
ctx.graphics.internal_projection = ortho(0.0, width as f32, height as f32, 0.0, -1.0, 1.0);
ctx.graphics.backbuffer_texture =
Texture::from_handle(ctx.gl.new_texture(width, height, TextureFormat::Rgb));
ctx.gl.attach_texture_to_framebuffer(
&ctx.graphics.backbuffer,
&ctx.graphics.backbuffer_texture.handle,
true,
);
ctx.gl.set_viewport(0, 0, width, height);
}
pub(crate) fn update_screen_rect(ctx: &mut Context) {
ctx.graphics.screen_rect = ctx.graphics.scaling.get_screen_rect(
ctx.graphics.backbuffer_texture.width(),
ctx.graphics.backbuffer_texture.height(),
window::get_width(ctx),
window::get_height(ctx),
);
}
pub(crate) fn set_window_projection(ctx: &mut Context, width: i32, height: i32) {
ctx.graphics.window_projection = ortho(0.0, width as f32, height as f32, 0.0, -1.0, 1.0);
}
pub(crate) fn ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 {
let c0r0 = 2.0 / (right - left);
let c0r1 = 0.0;
let c0r2 = 0.0;
let c0r3 = 0.0;
let c1r0 = 0.0;
let c1r1 = 2.0 / (top - bottom);
let c1r2 = 0.0;
let c1r3 = 0.0;
let c2r0 = 0.0;
let c2r1 = 0.0;
let c2r2 = -2.0 / (far - near);
let c2r3 = 0.0;
let c3r0 = -(right + left) / (right - left);
let c3r1 = -(top + bottom) / (top - bottom);
let c3r2 = -(far + near) / (far - near);
let c3r3 = 1.0;
Mat4::from([
[c0r0, c0r1, c0r2, c0r3],
[c1r0, c1r1, c1r2, c1r3],
[c2r0, c2r1, c2r2, c2r3],
[c3r0, c3r1, c3r2, c3r3],
])
}