use std::collections::HashMap;
use std::convert::From;
use std::fmt;
use std::path::Path;
use std::u16;
use gfx;
use gfx::texture;
use gfx::Device;
use gfx::Factory;
use gfx_device_gl;
use glutin;
use crate::conf;
use crate::conf::WindowMode;
use crate::context::Context;
use crate::context::DebugId;
use crate::GameError;
use crate::GameResult;
pub(crate) mod canvas;
pub(crate) mod context;
pub(crate) mod drawparam;
pub(crate) mod image;
pub(crate) mod mesh;
pub(crate) mod shader;
pub(crate) mod text;
pub(crate) mod types;
use mint;
pub(crate) use nalgebra as na;
pub mod spritebatch;
pub use crate::graphics::canvas::*;
pub use crate::graphics::drawparam::*;
pub use crate::graphics::image::*;
pub use crate::graphics::mesh::*;
pub use crate::graphics::shader::*;
pub use crate::graphics::text::*;
pub use crate::graphics::types::*;
pub(crate) type BuggoSurfaceFormat = gfx::format::Srgba8;
type ShaderResourceType = [f32; 4];
pub trait BackendSpec: fmt::Debug {
type Resources: gfx::Resources;
type Factory: gfx::Factory<Self::Resources> + Clone;
type CommandBuffer: gfx::CommandBuffer<Self::Resources>;
type Device: gfx::Device<Resources = Self::Resources, CommandBuffer = Self::CommandBuffer>;
fn raw_to_typed_shader_resource(
&self,
texture_view: gfx::handle::RawShaderResourceView<Self::Resources>,
) -> gfx::handle::ShaderResourceView<<Self as BackendSpec>::Resources, ShaderResourceType> {
let typed_view: gfx::handle::ShaderResourceView<_, ShaderResourceType> =
gfx::memory::Typed::new(texture_view);
typed_view
}
fn raw_to_typed_texture(
&self,
texture_view: gfx::handle::RawTexture<Self::Resources>,
) -> gfx::handle::Texture<
<Self as BackendSpec>::Resources,
<BuggoSurfaceFormat as gfx::format::Formatted>::Surface,
> {
let typed_view: gfx::handle::Texture<
_,
<BuggoSurfaceFormat as gfx::format::Formatted>::Surface,
> = gfx::memory::Typed::new(texture_view);
typed_view
}
fn version_tuple(&self) -> (u8, u8);
fn api(&self) -> glutin::Api;
fn shaders(&self) -> (&'static [u8], &'static [u8]);
fn info(&self, device: &Self::Device) -> String;
fn init<'a>(
&self,
window_builder: glutin::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a>,
events_loop: &glutin::EventsLoop,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
) -> Result<
(
glutin::WindowedContext,
Self::Device,
Self::Factory,
gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>,
),
glutin::CreationError,
>;
fn encoder(factory: &mut Self::Factory) -> gfx::Encoder<Self::Resources, Self::CommandBuffer>;
fn resize_viewport(
&self,
color_view: &gfx::handle::RawRenderTargetView<Self::Resources>,
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
window: &glutin::WindowedContext,
) -> Option<(
gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>,
)>;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, SmartDefault)]
pub struct GlBackendSpec {
#[default = 3]
major: u8,
#[default = 2]
minor: u8,
#[default(glutin::Api::OpenGl)]
api: glutin::Api,
}
impl From<conf::Backend> for GlBackendSpec {
fn from(c: conf::Backend) -> Self {
match c {
conf::Backend::OpenGL { major, minor } => Self {
major,
minor,
api: glutin::Api::OpenGl,
},
conf::Backend::OpenGLES { major, minor } => Self {
major,
minor,
api: glutin::Api::OpenGlEs,
},
}
}
}
impl BackendSpec for GlBackendSpec {
type Resources = gfx_device_gl::Resources;
type Factory = gfx_device_gl::Factory;
type CommandBuffer = gfx_device_gl::CommandBuffer;
type Device = gfx_device_gl::Device;
fn version_tuple(&self) -> (u8, u8) {
(self.major, self.minor)
}
fn api(&self) -> glutin::Api {
self.api
}
fn shaders(&self) -> (&'static [u8], &'static [u8]) {
match self.api {
glutin::Api::OpenGl => (
include_bytes!("shader/basic_150.glslv"),
include_bytes!("shader/basic_150.glslf"),
),
glutin::Api::OpenGlEs => (
include_bytes!("shader/basic_es300.glslv"),
include_bytes!("shader/basic_es300.glslf"),
),
a => panic!("Unsupported API: {:?}, should never happen", a),
}
}
fn init<'a>(
&self,
window_builder: glutin::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a>,
events_loop: &glutin::EventsLoop,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
) -> Result<
(
glutin::WindowedContext,
Self::Device,
Self::Factory,
gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>,
),
glutin::CreationError,
> {
use gfx_window_glutin;
gfx_window_glutin::init_raw(
window_builder,
gl_builder,
events_loop,
color_format,
depth_format,
)
}
fn info(&self, device: &Self::Device) -> String {
let info = device.get_info();
format!(
"Driver vendor: {}, renderer {}, version {:?}, shading language {:?}",
info.platform_name.vendor,
info.platform_name.renderer,
info.version,
info.shading_language
)
}
fn encoder(factory: &mut Self::Factory) -> gfx::Encoder<Self::Resources, Self::CommandBuffer> {
factory.create_command_buffer().into()
}
fn resize_viewport(
&self,
color_view: &gfx::handle::RawRenderTargetView<Self::Resources>,
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
window: &glutin::WindowedContext,
) -> Option<(
gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>,
)> {
let dim = color_view.get_dimensions();
assert_eq!(dim, depth_view.get_dimensions());
use gfx_window_glutin;
if let Some((cv, dv)) =
gfx_window_glutin::update_views_raw(window, dim, color_format, depth_format)
{
Some((cv, dv))
} else {
None
}
}
}
const QUAD_VERTS: [Vertex; 4] = [
Vertex {
pos: [0.0, 0.0],
uv: [0.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
pos: [1.0, 0.0],
uv: [1.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
pos: [1.0, 1.0],
uv: [1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
pos: [0.0, 1.0],
uv: [0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
];
const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
gfx_defines! {
vertex Vertex {
pos: [f32; 2] = "a_Pos",
uv: [f32; 2] = "a_Uv",
color: [f32;4] = "a_VertColor",
}
vertex InstanceProperties {
col1: [f32; 4] = "a_TCol1",
col2: [f32; 4] = "a_TCol2",
col3: [f32; 4] = "a_TCol3",
col4: [f32; 4] = "a_TCol4",
src: [f32; 4] = "a_Src",
color: [f32; 4] = "a_Color",
}
constant Globals {
mvp_matrix: [[f32; 4]; 4] = "u_MVP",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
tex: gfx::TextureSampler<[f32; 4]> = "t_Texture",
globals: gfx::ConstantBuffer<Globals> = "Globals",
rect_instance_properties: gfx::InstanceBuffer<InstanceProperties> = (),
out: gfx::RawRenderTarget =
("Target0",
gfx::format::Format(gfx::format::SurfaceType::R8_G8_B8_A8, gfx::format::ChannelType::Srgb),
gfx::state::ColorMask::all(), Some(gfx::preset::blend::ALPHA)
),
}
}
impl fmt::Display for InstanceProperties {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut matrix_vec: Vec<f32> = vec![];
matrix_vec.extend(&self.col1);
matrix_vec.extend(&self.col2);
matrix_vec.extend(&self.col3);
matrix_vec.extend(&self.col4);
let matrix = na::Matrix4::from_column_slice(&matrix_vec);
write!(
f,
"Src: ({},{}+{},{})\n",
self.src[0], self.src[1], self.src[2], self.src[3]
)?;
write!(f, "Color: {:?}\n", self.color)?;
write!(f, "Matrix: {}", matrix)
}
}
impl Default for InstanceProperties {
fn default() -> Self {
InstanceProperties {
col1: [1.0, 0.0, 0.0, 0.0],
col2: [0.0, 1.0, 0.0, 0.0],
col3: [1.0, 0.0, 1.0, 0.0],
col4: [1.0, 0.0, 0.0, 1.0],
src: [0.0, 0.0, 1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
}
}
}
pub(crate) struct SamplerCache<B>
where
B: BackendSpec,
{
samplers: HashMap<texture::SamplerInfo, gfx::handle::Sampler<B::Resources>>,
}
impl<B> SamplerCache<B>
where
B: BackendSpec,
{
fn new() -> Self {
SamplerCache {
samplers: HashMap::new(),
}
}
fn get_or_insert(
&mut self,
info: texture::SamplerInfo,
factory: &mut B::Factory,
) -> gfx::handle::Sampler<B::Resources> {
let sampler = self
.samplers
.entry(info)
.or_insert_with(|| factory.create_sampler(info));
sampler.clone()
}
}
impl From<gfx::buffer::CreationError> for GameError {
fn from(e: gfx::buffer::CreationError) -> Self {
use gfx::buffer::CreationError;
match e {
CreationError::UnsupportedBind(b) => GameError::RenderError(format!(
"Could not create buffer: Unsupported Bind ({:?})",
b
)),
CreationError::UnsupportedUsage(u) => GameError::RenderError(format!(
"Could not create buffer: Unsupported Usage ({:?})",
u
)),
CreationError::Other => {
GameError::RenderError("Could not create buffer: Unknown error".to_owned())
}
}
}
}
pub fn clear(ctx: &mut Context, color: Color) {
let gfx = &mut ctx.gfx_context;
let linear_color: types::LinearColor = color.into();
type ColorFormat = BuggoSurfaceFormat;
let typed_render_target: gfx::handle::RenderTargetView<_, ColorFormat> =
gfx::memory::Typed::new(gfx.data.out.clone());
gfx.encoder.clear(&typed_render_target, linear_color.into());
}
pub fn draw<D, T>(ctx: &mut Context, drawable: &D, params: T) -> GameResult
where
D: Drawable,
T: Into<DrawParam>,
{
let params = params.into();
drawable.draw(ctx, params)
}
pub fn present(ctx: &mut Context) -> GameResult<()> {
let gfx = &mut ctx.gfx_context;
gfx.data.out = gfx.screen_render_target.clone();
gfx.encoder.flush(&mut *gfx.device);
gfx.window.swap_buffers()?;
gfx.device.cleanup();
Ok(())
}
pub fn screenshot(ctx: &mut Context) -> GameResult<Image> {
use gfx::memory::Bind;
let debug_id = DebugId::get(ctx);
let gfx = &mut ctx.gfx_context;
let (w, h, _depth, aa) = gfx.data.out.get_dimensions();
let surface_format = gfx.color_format();
let gfx::format::Format(surface_type, channel_type) = surface_format;
let texture_kind = gfx::texture::Kind::D2(w, h, aa);
let texture_info = gfx::texture::Info {
kind: texture_kind,
levels: 1,
format: surface_type,
bind: Bind::TRANSFER_SRC | Bind::TRANSFER_DST | Bind::SHADER_RESOURCE,
usage: gfx::memory::Usage::Data,
};
let target_texture = gfx
.factory
.create_texture_raw(texture_info, Some(channel_type), None)?;
let image_info = gfx::texture::ImageInfoCommon {
xoffset: 0,
yoffset: 0,
zoffset: 0,
width: w,
height: h,
depth: 0,
format: surface_format,
mipmap: 0,
};
let mut local_encoder: gfx::Encoder<gfx_device_gl::Resources, gfx_device_gl::CommandBuffer> =
gfx.factory.create_command_buffer().into();
local_encoder.copy_texture_to_texture_raw(
gfx.data.out.get_texture(),
None,
image_info,
&target_texture,
None,
image_info,
)?;
local_encoder.flush(&mut *gfx.device);
let resource_desc = gfx::texture::ResourceDesc {
channel: channel_type,
layer: None,
min: 0,
max: 0,
swizzle: gfx::format::Swizzle::new(),
};
let shader_resource = gfx
.factory
.view_texture_as_shader_resource_raw(&target_texture, resource_desc)?;
let image = Image {
texture: shader_resource,
texture_handle: target_texture,
sampler_info: gfx.default_sampler_info,
blend_mode: None,
width: w,
height: h,
debug_id,
};
Ok(image)
}
pub fn default_filter(ctx: &Context) -> FilterMode {
let gfx = &ctx.gfx_context;
gfx.default_sampler_info.filter.into()
}
pub fn renderer_info(ctx: &Context) -> GameResult<String> {
let backend_info = ctx.gfx_context.backend_spec.info(&*ctx.gfx_context.device);
Ok(format!(
"Requested {:?} {}.{} Core profile, actually got {}.",
ctx.gfx_context.backend_spec.api,
ctx.gfx_context.backend_spec.major,
ctx.gfx_context.backend_spec.minor,
backend_info
))
}
pub fn screen_coordinates(ctx: &Context) -> Rect {
ctx.gfx_context.screen_rect
}
pub fn set_default_filter(ctx: &mut Context, mode: FilterMode) {
let gfx = &mut ctx.gfx_context;
let new_mode = mode.into();
let sampler_info = texture::SamplerInfo::new(new_mode, texture::WrapMode::Clamp);
let _sampler = gfx.samplers.get_or_insert(sampler_info, &mut *gfx.factory);
gfx.default_sampler_info = sampler_info;
}
pub fn set_screen_coordinates(context: &mut Context, rect: Rect) -> GameResult {
let gfx = &mut context.gfx_context;
gfx.set_projection_rect(rect);
gfx.calculate_transform_matrix();
gfx.update_globals()
}
pub fn set_projection<M>(context: &mut Context, proj: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let proj = Matrix4::from(proj.into());
let gfx = &mut context.gfx_context;
gfx.set_projection(proj);
}
pub fn mul_projection<M>(context: &mut Context, transform: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = Matrix4::from(transform.into());
let gfx = &mut context.gfx_context;
let curr = gfx.projection();
gfx.set_projection(transform * curr);
}
pub fn projection(context: &Context) -> mint::ColumnMatrix4<f32> {
let gfx = &context.gfx_context;
gfx.projection().into()
}
pub fn push_transform<M>(context: &mut Context, transform: Option<M>)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = transform.map(|transform| Matrix4::from(transform.into()));
let gfx = &mut context.gfx_context;
if let Some(t) = transform {
gfx.push_transform(t);
} else {
let copy = *gfx
.modelview_stack
.last()
.expect("Matrix stack empty, should never happen");
gfx.push_transform(copy);
}
}
pub fn pop_transform(context: &mut Context) {
let gfx = &mut context.gfx_context;
gfx.pop_transform();
}
pub fn set_transform<M>(context: &mut Context, transform: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = transform.into();
let gfx = &mut context.gfx_context;
gfx.set_transform(Matrix4::from(transform));
}
pub fn transform(context: &Context) -> mint::ColumnMatrix4<f32> {
let gfx = &context.gfx_context;
gfx.transform().into()
}
pub fn mul_transform<M>(context: &mut Context, transform: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = Matrix4::from(transform.into());
let gfx = &mut context.gfx_context;
let curr = gfx.transform();
gfx.set_transform(transform * curr);
}
pub fn origin(context: &mut Context) {
let gfx = &mut context.gfx_context;
gfx.set_transform(Matrix4::identity());
}
pub fn apply_transformations(context: &mut Context) -> GameResult {
let gfx = &mut context.gfx_context;
gfx.calculate_transform_matrix();
gfx.update_globals()
}
pub fn set_blend_mode(ctx: &mut Context, mode: BlendMode) -> GameResult {
ctx.gfx_context.set_blend_mode(mode)
}
pub fn set_mode(context: &mut Context, mode: WindowMode) -> GameResult {
let gfx = &mut context.gfx_context;
gfx.set_window_mode(mode)?;
context.conf.window_mode = mode;
Ok(())
}
pub fn set_fullscreen(context: &mut Context, fullscreen: conf::FullscreenType) -> GameResult {
let mut window_mode = context.conf.window_mode;
window_mode.fullscreen_type = fullscreen;
set_mode(context, window_mode)
}
pub fn set_drawable_size(context: &mut Context, width: f32, height: f32) -> GameResult {
let mut window_mode = context.conf.window_mode;
window_mode.width = width;
window_mode.height = height;
set_mode(context, window_mode)
}
pub fn set_resizable(context: &mut Context, resizable: bool) -> GameResult {
let mut window_mode = context.conf.window_mode;
window_mode.resizable = resizable;
set_mode(context, window_mode)
}
pub fn set_window_icon<P: AsRef<Path>>(context: &mut Context, path: Option<P>) -> GameResult<()> {
let icon = match path {
Some(p) => {
let p: &Path = p.as_ref();
Some(context::load_icon(p, &mut context.filesystem)?)
}
None => None,
};
context.gfx_context.window.set_window_icon(icon);
Ok(())
}
pub fn set_window_title(context: &Context, title: &str) {
context.gfx_context.window.set_title(title);
}
pub fn window(context: &Context) -> &glutin::Window {
let gfx = &context.gfx_context;
&gfx.window
}
pub fn size(context: &Context) -> (f32, f32) {
let gfx = &context.gfx_context;
gfx.window
.get_outer_size()
.map(|logical_size| logical_size.to_physical(context.gfx_context.hidpi_factor as f64))
.map(|physical_size| (physical_size.width as f32, physical_size.height as f32))
.unwrap_or((0.0, 0.0))
}
pub fn hidpi_factor(context: &Context) -> f32 {
context.gfx_context.hidpi_factor
}
pub fn drawable_size(context: &Context) -> (f32, f32) {
let gfx = &context.gfx_context;
gfx.window
.get_inner_size()
.map(|logical_size| logical_size.to_physical(context.gfx_context.hidpi_factor as f64))
.map(|physical_size| (physical_size.width as f32, physical_size.height as f32))
.unwrap_or((0.0, 0.0))
}
pub fn factory(context: &mut Context) -> &mut gfx_device_gl::Factory {
let gfx = &mut context.gfx_context;
&mut gfx.factory
}
pub fn device(context: &mut Context) -> &mut gfx_device_gl::Device {
let gfx = &mut context.gfx_context;
gfx.device.as_mut()
}
pub fn encoder(
context: &mut Context,
) -> &mut gfx::Encoder<gfx_device_gl::Resources, gfx_device_gl::CommandBuffer> {
let gfx = &mut context.gfx_context;
&mut gfx.encoder
}
pub fn depth_view(
context: &mut Context,
) -> gfx::handle::RawDepthStencilView<gfx_device_gl::Resources> {
let gfx = &mut context.gfx_context;
gfx.depth_view.clone()
}
pub fn screen_render_target(
context: &Context,
) -> gfx::handle::RawRenderTargetView<gfx_device_gl::Resources> {
let gfx = &context.gfx_context;
gfx.data.out.clone()
}
pub fn gfx_objects(
context: &mut Context,
) -> (
&mut <GlBackendSpec as BackendSpec>::Factory,
&mut <GlBackendSpec as BackendSpec>::Device,
&mut gfx::Encoder<
<GlBackendSpec as BackendSpec>::Resources,
<GlBackendSpec as BackendSpec>::CommandBuffer,
>,
gfx::handle::RawDepthStencilView<<GlBackendSpec as BackendSpec>::Resources>,
gfx::handle::RawRenderTargetView<<GlBackendSpec as BackendSpec>::Resources>,
) {
let gfx = &mut context.gfx_context;
let f = &mut gfx.factory;
let d = gfx.device.as_mut();
let e = &mut gfx.encoder;
let dv = gfx.depth_view.clone();
let cv = gfx.data.out.clone();
(f, d, e, dv, cv)
}
pub trait Drawable {
fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult;
fn dimensions(&self, ctx: &mut Context) -> Option<Rect>;
fn set_blend_mode(&mut self, mode: Option<BlendMode>);
fn blend_mode(&self) -> Option<BlendMode>;
}
pub fn transform_rect(rect: Rect, param: DrawParam) -> Rect {
let w = param.src.w * param.scale.x * rect.w;
let h = param.src.h * param.scale.y * rect.h;
let offset_x = w * param.offset.x;
let offset_y = h * param.offset.y;
let dest_x = param.dest.x - offset_x;
let dest_y = param.dest.y - offset_y;
let mut r = Rect {
w,
h,
x: dest_x + rect.x * param.scale.x,
y: dest_y + rect.y * param.scale.y,
};
r.rotate(param.rotation);
r
}
#[cfg(test)]
mod tests {
use crate::graphics::{transform_rect, DrawParam, Rect};
use approx::assert_relative_eq;
use std::f32::consts::PI;
#[test]
fn headless_test_transform_rect() {
{
let r = Rect {
x: 0.0,
y: 0.0,
w: 1.0,
h: 1.0,
};
let param = DrawParam::new();
let real = transform_rect(r, param);
let expected = r;
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new().scale([0.5, 0.5]);
let real = transform_rect(r, param);
let expected = Rect {
x: -0.5,
y: -0.5,
w: 1.0,
h: 0.5,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 1.0,
h: 1.0,
};
let param = DrawParam::new().offset([0.5, 0.5]);
let real = transform_rect(r, param);
let expected = Rect {
x: -1.5,
y: -1.5,
w: 1.0,
h: 1.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: 1.0,
y: 0.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new().rotation(PI * 0.5);
let real = transform_rect(r, param);
let expected = Rect {
x: -1.0,
y: 1.0,
w: 1.0,
h: 2.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new()
.scale([0.5, 0.5])
.offset([0.0, 1.0])
.rotation(PI * 0.5);
let real = transform_rect(r, param);
let expected = Rect {
x: 0.5,
y: -0.5,
w: 0.5,
h: 1.0,
};
assert_relative_eq!(real, expected);
}
}
}