pub mod animation;
mod camera;
mod canvas;
mod color;
mod drawparams;
mod image_data;
pub mod mesh;
mod rectangle;
pub mod scaling;
mod shader;
pub mod text;
mod texture;
pub use camera::*;
pub use canvas::*;
pub use color::*;
pub use drawparams::*;
pub use image_data::*;
pub use rectangle::*;
pub use shader::*;
pub use texture::*;
use crate::error::Result;
use crate::math::{FrustumPlanes, Mat4, Vec2};
use crate::platform::{GraphicsDevice, RawIndexBuffer, RawVertexBuffer};
use crate::window;
use crate::Context;
use self::mesh::{BufferUsage, Vertex, VertexWinding};
const MAX_SPRITES: usize = 2048;
const MAX_VERTICES: usize = MAX_SPRITES * 4; const MAX_INDICES: usize = MAX_SPRITES * 6;
const INDEX_ARRAY: [u32; 6] = [0, 1, 2, 2, 3, 0];
pub(crate) struct GraphicsContext {
vertex_buffer: RawVertexBuffer,
index_buffer: RawIndexBuffer,
texture: Option<Texture>,
default_texture: Texture,
default_filter_mode: FilterMode,
shader: Option<Shader>,
default_shader: Shader,
canvas: Option<Canvas>,
projection_matrix: Mat4<f32>,
transform_matrix: Mat4<f32>,
vertex_data: Vec<Vertex>,
element_count: usize,
blend_state: BlendState,
}
impl GraphicsContext {
pub(crate) fn new(
device: &mut GraphicsDevice,
window_width: i32,
window_height: i32,
) -> Result<GraphicsContext> {
let vertex_buffer = device.new_vertex_buffer(MAX_VERTICES, BufferUsage::Dynamic)?;
let index_buffer = device.new_index_buffer(MAX_INDICES, BufferUsage::Static)?;
let indices: Vec<u32> = INDEX_ARRAY
.iter()
.cycle()
.take(MAX_INDICES)
.enumerate()
.map(|(i, vertex)| vertex + i as u32 / 6 * 4)
.collect();
device.set_index_buffer_data(&index_buffer, &indices, 0);
let default_texture = Texture::with_device(
device,
1,
1,
&[255, 255, 255, 255],
TextureFormat::Rgba8,
FilterMode::Nearest,
)?;
let default_filter_mode = FilterMode::Nearest;
let default_shader = Shader::with_device(
device,
shader::DEFAULT_VERTEX_SHADER,
shader::DEFAULT_FRAGMENT_SHADER,
)?;
Ok(GraphicsContext {
vertex_buffer,
index_buffer,
texture: None,
default_texture,
default_filter_mode,
shader: None,
default_shader,
canvas: None,
projection_matrix: ortho(window_width as f32, window_height as f32, false),
transform_matrix: Mat4::identity(),
vertex_data: Vec::with_capacity(MAX_VERTICES),
element_count: 0,
blend_state: BlendState::default(),
})
}
}
pub fn clear(ctx: &mut Context, color: Color) {
ctx.device.clear(color);
}
#[allow(clippy::too_many_arguments)]
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 + 6 > MAX_INDICES {
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 {
(
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,
)
} else {
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),
)
};
ctx.graphics.vertex_data.extend_from_slice(&[
Vertex::new(Vec2::new(ox1, oy1), Vec2::new(u1, v1), params.color),
Vertex::new(Vec2::new(ox2, oy2), Vec2::new(u1, v2), params.color),
Vertex::new(Vec2::new(ox3, oy3), Vec2::new(u2, v2), params.color),
Vertex::new(Vec2::new(ox4, oy4), Vec2::new(u2, v1), params.color),
]);
ctx.graphics.element_count += 6;
}
pub(crate) fn set_texture(ctx: &mut Context, texture: &Texture) {
set_texture_ex(ctx, Some(texture));
}
pub(crate) fn set_texture_ex(ctx: &mut Context, texture: Option<&Texture>) {
if texture != ctx.graphics.texture.as_ref() {
flush(ctx);
ctx.graphics.texture = texture.cloned();
}
}
pub fn set_blend_state(ctx: &mut Context, blend_state: BlendState) {
if blend_state != ctx.graphics.blend_state {
flush(ctx);
ctx.graphics.blend_state = blend_state;
ctx.device.set_blend_state(blend_state);
}
}
pub fn reset_blend_state(ctx: &mut Context) {
set_blend_state(ctx, Default::default());
}
pub fn set_shader(ctx: &mut Context, shader: &Shader) {
set_shader_ex(ctx, Some(shader));
}
pub fn reset_shader(ctx: &mut Context) {
set_shader_ex(ctx, None);
}
pub(crate) fn set_shader_ex(ctx: &mut Context, shader: Option<&Shader>) {
if shader != ctx.graphics.shader.as_ref() {
flush(ctx);
ctx.graphics.shader = shader.cloned();
}
}
pub fn set_canvas(ctx: &mut Context, canvas: &Canvas) {
set_canvas_ex(ctx, Some(canvas));
}
pub fn reset_canvas(ctx: &mut Context) {
set_canvas_ex(ctx, None);
}
pub(crate) fn set_canvas_ex(ctx: &mut Context, canvas: Option<&Canvas>) {
if canvas != ctx.graphics.canvas.as_ref() {
flush(ctx);
resolve_canvas(ctx);
ctx.graphics.canvas = canvas.cloned();
match &ctx.graphics.canvas {
None => {
let (width, height) = window::get_size(ctx);
let (physical_width, physical_height) = window::get_physical_size(ctx);
ctx.graphics.projection_matrix = ortho(width as f32, height as f32, false);
ctx.device.viewport(0, 0, physical_width, physical_height);
ctx.device.set_canvas(None);
}
Some(r) => {
let (width, height) = r.size();
ctx.graphics.projection_matrix = ortho(width as f32, height as f32, true);
ctx.device.viewport(0, 0, width, height);
ctx.device.set_canvas(Some(&r.handle));
}
}
}
}
fn resolve_canvas(ctx: &mut Context) {
if let Some(c) = &ctx.graphics.canvas {
if c.multisample.is_some() {
ctx.device.resolve(&c.handle, &c.texture.data.handle);
}
}
}
pub fn flush(ctx: &mut Context) {
if !ctx.graphics.vertex_data.is_empty() {
let texture = match &ctx.graphics.texture {
None => return,
Some(t) => t,
};
let shader = ctx
.graphics
.shader
.as_ref()
.unwrap_or(&ctx.graphics.default_shader);
let _ = shader.set_default_uniforms(
&mut ctx.device,
ctx.graphics.projection_matrix * ctx.graphics.transform_matrix,
Color::WHITE,
);
ctx.device.cull_face(true);
ctx.device.front_face(match &ctx.graphics.canvas {
None => VertexWinding::CounterClockwise,
Some(_) => VertexWinding::Clockwise,
});
ctx.device.set_vertex_buffer_data(
&ctx.graphics.vertex_buffer,
&ctx.graphics.vertex_data,
0,
);
ctx.device.draw(
&ctx.graphics.vertex_buffer,
Some(&ctx.graphics.index_buffer),
&texture.data.handle,
&shader.data.handle,
0,
ctx.graphics.element_count,
);
ctx.graphics.vertex_data.clear();
ctx.graphics.element_count = 0;
}
}
pub fn present(ctx: &mut Context) {
flush(ctx);
ctx.window.swap_buffers();
}
pub fn get_default_filter_mode(ctx: &Context) -> FilterMode {
ctx.graphics.default_filter_mode
}
pub fn set_default_filter_mode(ctx: &mut Context, filter_mode: FilterMode) {
ctx.graphics.default_filter_mode = filter_mode;
}
#[derive(Debug, Clone)]
pub struct GraphicsDeviceInfo {
pub vendor: String,
pub renderer: String,
pub opengl_version: String,
pub glsl_version: String,
}
pub fn get_device_info(ctx: &Context) -> GraphicsDeviceInfo {
ctx.device.get_info()
}
pub fn get_transform_matrix(ctx: &Context) -> Mat4<f32> {
ctx.graphics.transform_matrix
}
pub fn set_transform_matrix(ctx: &mut Context, matrix: Mat4<f32>) {
flush(ctx);
ctx.graphics.transform_matrix = matrix;
}
pub fn reset_transform_matrix(ctx: &mut Context) {
set_transform_matrix(ctx, Mat4::identity());
}
pub fn set_scissor(ctx: &mut Context, scissor_rect: Rectangle<i32>) {
flush(ctx);
match &ctx.graphics.canvas {
None => {
let physical_height = window::get_physical_height(ctx);
ctx.device.scissor(
scissor_rect.x,
physical_height - (scissor_rect.y + scissor_rect.height),
scissor_rect.width,
scissor_rect.height,
);
}
Some(_) => {
ctx.device.scissor(
scissor_rect.x,
scissor_rect.y,
scissor_rect.width,
scissor_rect.height,
);
}
}
ctx.device.scissor_test(true);
}
pub fn reset_scissor(ctx: &mut Context) {
flush(ctx);
ctx.device.scissor_test(false);
}
pub fn set_stencil_state(ctx: &mut Context, state: StencilState) {
flush(ctx);
ctx.device.set_stencil_state(state);
}
pub fn clear_stencil(ctx: &mut Context, value: u8) {
flush(ctx);
ctx.device.clear_stencil(value);
}
pub fn set_color_mask(ctx: &mut Context, red: bool, green: bool, blue: bool, alpha: bool) {
flush(ctx);
ctx.device.set_color_mask(red, green, blue, alpha);
}
pub(crate) fn set_viewport_size(ctx: &mut Context) {
if ctx.graphics.canvas.is_none() {
let (width, height) = window::get_size(ctx);
let (physical_width, physical_height) = window::get_physical_size(ctx);
ctx.graphics.projection_matrix = ortho(width as f32, height as f32, false);
ctx.device.viewport(0, 0, physical_width, physical_height);
}
}
pub(crate) fn ortho(width: f32, height: f32, flipped: bool) -> Mat4<f32> {
Mat4::orthographic_rh_no(FrustumPlanes {
left: 0.0,
right: width,
bottom: if flipped { 0.0 } else { height },
top: if flipped { height } else { 0.0 },
near: -1.0,
far: 1.0,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlendOperation {
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlendFactor {
Zero,
One,
Src,
OneMinusSrc,
SrcAlpha,
OneMinusSrcAlpha,
Dst,
OneMinusDst,
DstAlpha,
OneMinusDstAlpha,
SrcAlphaSaturated,
Constant,
OneMinusConstant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlendState {
pub color_operation: BlendOperation,
pub color_src: BlendFactor,
pub color_dst: BlendFactor,
pub alpha_operation: BlendOperation,
pub alpha_src: BlendFactor,
pub alpha_dst: BlendFactor,
}
impl BlendState {
pub const fn alpha(premultiplied: bool) -> BlendState {
let color_src = if premultiplied {
BlendFactor::One
} else {
BlendFactor::SrcAlpha
};
BlendState {
color_operation: BlendOperation::Add,
color_src,
color_dst: BlendFactor::OneMinusSrcAlpha,
alpha_operation: BlendOperation::Add,
alpha_src: BlendFactor::One,
alpha_dst: BlendFactor::OneMinusSrcAlpha,
}
}
pub const fn add(premultiplied: bool) -> BlendState {
let color_src = if premultiplied {
BlendFactor::One
} else {
BlendFactor::SrcAlpha
};
BlendState {
color_operation: BlendOperation::Add,
color_src,
color_dst: BlendFactor::One,
alpha_operation: BlendOperation::Add,
alpha_src: BlendFactor::Zero,
alpha_dst: BlendFactor::One,
}
}
pub const fn subtract(premultiplied: bool) -> BlendState {
let color_src = if premultiplied {
BlendFactor::One
} else {
BlendFactor::SrcAlpha
};
BlendState {
color_operation: BlendOperation::ReverseSubtract,
color_src,
color_dst: BlendFactor::One,
alpha_operation: BlendOperation::ReverseSubtract,
alpha_src: BlendFactor::Zero,
alpha_dst: BlendFactor::One,
}
}
pub const fn multiply() -> BlendState {
BlendState {
color_operation: BlendOperation::Add,
color_src: BlendFactor::Dst,
color_dst: BlendFactor::Zero,
alpha_operation: BlendOperation::Add,
alpha_src: BlendFactor::Dst,
alpha_dst: BlendFactor::Zero,
}
}
}
impl Default for BlendState {
fn default() -> Self {
BlendState::alpha(false)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StencilTest {
Never,
LessThan,
LessThanOrEqualTo,
EqualTo,
NotEqualTo,
GreaterThan,
GreaterThanOrEqualTo,
Always,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StencilAction {
Keep,
Zero,
Replace,
Increment,
IncrementWrap,
Decrement,
DecrementWrap,
Invert,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StencilState {
pub enabled: bool,
pub action: StencilAction,
pub test: StencilTest,
pub reference_value: u8,
pub write_mask: u8,
pub read_mask: u8,
}
impl StencilState {
pub fn disabled() -> Self {
Self {
enabled: false,
action: StencilAction::Keep,
test: StencilTest::Always,
reference_value: 0,
write_mask: 0x00,
read_mask: 0x00,
}
}
pub fn write(action: StencilAction, reference_value: u8) -> Self {
Self {
enabled: true,
action,
test: StencilTest::Always,
reference_value,
write_mask: 0xFF,
read_mask: 0xFF,
}
}
pub fn read(test: StencilTest, reference_value: u8) -> Self {
Self {
enabled: true,
action: StencilAction::Keep,
test,
reference_value,
write_mask: 0xFF,
read_mask: 0xFF,
}
}
}