pub mod animation;
mod canvas;
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::canvas::*;
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 crate::glm::Vec2;
use glyph_brush::{GlyphBrush, GlyphBrushBuilder};
use crate::error::Result;
use crate::glm::{self, Mat3, Mat4};
use crate::graphics::opengl::{
BufferUsage, FrontFace, GLDevice, GLIndexBuffer, GLVertexBuffer, TextureFormat,
};
use crate::graphics::text::FontQuad;
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 {
Backbuffer,
FontCache,
User(Texture),
}
#[derive(PartialEq)]
pub(crate) enum ActiveShader {
Default,
User(Shader),
}
#[derive(PartialEq)]
pub(crate) enum ActiveCanvas {
Backbuffer,
Window,
User(Canvas),
}
pub(crate) struct GraphicsContext {
vertex_buffer: GLVertexBuffer,
index_buffer: GLIndexBuffer,
texture: Option<ActiveTexture>,
font_cache_texture: Texture,
shader: ActiveShader,
default_shader: Shader,
window_projection: Mat4,
canvas: ActiveCanvas,
backbuffer: Canvas,
vertex_data: Vec<f32>,
element_capacity: usize,
element_count: usize,
internal_width: i32,
internal_height: i32,
scaling: ScreenScaling,
screen_rect: Rectangle,
letterbox_color: Color,
font_cache: GlyphBrush<'static, FontQuad>,
}
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> {
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 = Canvas::with_device(device, backbuffer_width, backbuffer_height, false);
device.set_viewport(0, 0, backbuffer_width, backbuffer_height);
device.front_face(FrontFace::Clockwise);
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::with_device(
device,
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::with_device_empty(device, width as i32, height as i32);
Ok(GraphicsContext {
vertex_buffer,
index_buffer,
texture: None,
font_cache_texture,
shader: ActiveShader::Default,
default_shader,
window_projection: glm::ortho(
0.0,
window_width as f32,
window_height as f32,
0.0,
-1.0,
1.0,
),
canvas: ActiveCanvas::Backbuffer,
backbuffer,
vertex_data: Vec::with_capacity(MAX_VERTICES * VERTEX_STRIDE),
element_capacity: MAX_INDICES,
element_count: 0,
internal_width,
internal_height,
scaling,
screen_rect,
letterbox_color: color::BLACK,
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);
}
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,
) {
if ctx.graphics.element_count >= ctx.graphics.element_capacity {
flush(ctx);
}
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,
)
};
ctx.graphics.vertex_data.extend_from_slice(&[
ox1,
oy1,
u1,
v1,
params.color.r,
params.color.g,
params.color.b,
params.color.a,
ox2,
oy2,
u1,
v2,
params.color.r,
params.color.g,
params.color.b,
params.color.a,
ox3,
oy3,
u2,
v2,
params.color.r,
params.color.g,
params.color.b,
params.color.a,
ox4,
oy4,
u2,
v1,
params.color.r,
params.color.g,
params.color.b,
params.color.a,
]);
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) {
if shader != ctx.graphics.shader {
flush(ctx);
ctx.graphics.shader = shader;
}
}
pub fn set_canvas(ctx: &mut Context, canvas: &Canvas) {
set_canvas_ex(ctx, ActiveCanvas::User(canvas.clone()));
}
pub fn reset_canvas(ctx: &mut Context) {
set_canvas_ex(ctx, ActiveCanvas::Backbuffer);
}
pub(crate) fn set_canvas_ex(ctx: &mut Context, canvas: ActiveCanvas) {
if canvas != ctx.graphics.canvas {
flush(ctx);
ctx.graphics.canvas = canvas;
match &ctx.graphics.canvas {
ActiveCanvas::Window => {
ctx.gl.bind_default_framebuffer();
ctx.gl.front_face(FrontFace::CounterClockwise);
ctx.gl
.set_viewport(0, 0, window::get_width(ctx), window::get_height(ctx));
}
ActiveCanvas::Backbuffer => {
ctx.gl
.bind_framebuffer(&ctx.graphics.backbuffer.framebuffer);
ctx.gl.front_face(FrontFace::Clockwise);
ctx.gl.set_viewport(
0,
0,
ctx.graphics.backbuffer.width(),
ctx.graphics.backbuffer.height(),
);
}
ActiveCanvas::User(r) => {
ctx.gl.bind_framebuffer(&r.framebuffer);
ctx.gl.front_face(FrontFace::Clockwise);
ctx.gl.set_viewport(0, 0, r.width(), r.height());
}
}
}
}
pub fn flush(ctx: &mut Context) {
if !ctx.graphics.vertex_data.is_empty() {
let texture = match &ctx.graphics.texture {
None => return,
Some(ActiveTexture::Backbuffer) => &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.canvas {
ActiveCanvas::Window => &ctx.graphics.window_projection,
ActiveCanvas::Backbuffer => &ctx.graphics.backbuffer.projection,
ActiveCanvas::User(r) => &r.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.element_count = 0;
}
}
pub fn present(ctx: &mut Context) {
set_canvas_ex(ctx, ActiveCanvas::Window);
set_shader_ex(ctx, ActiveShader::Default);
set_texture_ex(ctx, ActiveTexture::Backbuffer);
clear(ctx, ctx.graphics.letterbox_color);
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,
0.0,
1.0,
1.0,
&DrawParams::new(),
);
flush(ctx);
ctx.window.gl_swap_window();
set_canvas_ex(ctx, ActiveCanvas::Backbuffer);
}
pub fn get_internal_width(ctx: &Context) -> i32 {
ctx.graphics.backbuffer.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.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.width(),
ctx.graphics.backbuffer.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 fn set_letterbox_color(ctx: &mut Context, color: Color) {
ctx.graphics.letterbox_color = color;
}
pub(crate) fn set_backbuffer_size(ctx: &mut Context, width: i32, height: i32) {
if ctx.graphics.backbuffer.width() != width || ctx.graphics.backbuffer.height() != height {
ctx.graphics.backbuffer = Canvas::new(ctx, width, height);
if let ActiveCanvas::Backbuffer = ctx.graphics.canvas {
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.width(),
ctx.graphics.backbuffer.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 = glm::ortho(0.0, width as f32, height as f32, 0.0, -1.0, 1.0);
}