#![deny(missing_docs)]
#[macro_use]
extern crate gfx;
extern crate freetype;
use std::collections::hash_map::{Entry, HashMap};
use std::marker::PhantomData;
use gfx::{CombinedError, CommandBuffer, Encoder, Factory, PipelineStateError, Resources, UpdateError};
use gfx::shade::ProgramError;
use gfx::handle::{Buffer, RenderTargetView};
use gfx::pso::PipelineState;
use gfx::texture;
use gfx::traits::FactoryExt;
mod font;
use font::BitmapFont;
pub use font::FontError;
const DEFAULT_FONT_SIZE: u8 = 16;
const DEFAULT_BUFFER_SIZE: usize = 128;
const DEFAULT_OUTLINE_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
const DEFAULT_PROJECTION: [[f32; 4]; 4] = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
#[cfg(feature = "include-font")]
const DEFAULT_FONT_DATA: Option<&'static [u8]> =
Some(include_bytes!("../assets/NotoSans-Regular.ttf"));
#[cfg(not(feature = "include-font"))]
const DEFAULT_FONT_DATA: Option<&'static [u8]> =
None;
#[derive(Debug)]
pub enum Error {
FontError(FontError),
PipelineError(PipelineStateError<String>),
ProgramError(ProgramError),
CombinedError(CombinedError),
UpdateError(UpdateError<usize>),
}
#[derive(PartialEq)]
pub enum HorizontalAnchor {
Left,
Center,
Right,
}
#[derive(PartialEq)]
pub enum VerticalAnchor {
Top,
Center,
Bottom,
}
impl From<FontError> for Error {
fn from(e: FontError) -> Error { Error::FontError(e) }
}
impl From<PipelineStateError<String>> for Error {
fn from(e: PipelineStateError<String>) -> Error { Error::PipelineError(e) }
}
impl From<ProgramError> for Error {
fn from(e: ProgramError) -> Error { Error::ProgramError(e) }
}
impl From<CombinedError> for Error {
fn from(e: CombinedError) -> Error { Error::CombinedError(e) }
}
impl From<UpdateError<usize>> for Error {
fn from(e: UpdateError<usize>) -> Error { Error::UpdateError(e) }
}
type IndexT = u32;
pub struct Renderer<R: Resources, F: Factory<R>> {
factory: F,
pso_map: HashMap<gfx::format::Format, PipelineState<R, pipe::Meta>>,
shaders: gfx::ShaderSet<R>,
vertex_data: Vec<Vertex>,
vertex_buffer: Buffer<R, Vertex>,
index_data: Vec<IndexT>,
index_buffer: Buffer<R, IndexT>,
font_bitmap: BitmapFont,
color: (gfx::handle::ShaderResourceView<R, f32>, gfx::handle::Sampler<R>),
}
pub struct RendererBuilder<'r, R: Resources, F: Factory<R>> {
factory: F,
font_size: u8,
font_path: Option<&'r str>,
font_data: Option<&'r [u8]>,
outline_width: Option<u8>,
outline_color: [f32; 4],
buffer_size: usize,
chars: Option<&'r [char]>,
_r: PhantomData<R>,
}
pub fn new<'r, R: Resources, F: Factory<R>>(factory: F) -> RendererBuilder<'r, R, F> {
RendererBuilder::new(factory)
}
impl<'r, R: Resources, F: Factory<R>> RendererBuilder<'r, R, F> {
pub fn new(factory: F) -> Self {
RendererBuilder {
factory: factory,
font_size: DEFAULT_FONT_SIZE,
font_path: None,
font_data: DEFAULT_FONT_DATA,
outline_width: None,
outline_color: DEFAULT_OUTLINE_COLOR,
buffer_size: DEFAULT_BUFFER_SIZE,
chars: None,
_r: PhantomData,
}
}
pub fn with_size(mut self, size: u8) -> Self {
self.font_size = size;
self
}
pub fn with_font(mut self, path: &'r str) -> Self {
self.font_path = Some(path);
self
}
pub fn with_font_data(mut self, data: &'r [u8]) -> Self {
self.font_data = Some(data);
self
}
pub fn with_outline(mut self, width: u8, color: [f32; 4]) -> Self {
self.outline_width = Some(width);
self.outline_color = color;
self
}
pub fn with_buffer_size(mut self, size: usize) -> Self {
self.buffer_size = size;
self
}
pub fn with_chars(mut self, chars: &'r [char]) -> Self {
self.chars = Some(chars);
self
}
pub fn build(mut self) -> Result<Renderer<R, F>, Error> {
use gfx::buffer;
use gfx::memory;
let vertex_buffer = self.factory.create_buffer(
self.buffer_size,
buffer::Role::Vertex,
memory::Usage::Dynamic,
memory::Bind::empty()
).expect("Could not create vertex buffer");
let index_buffer = self.factory.create_buffer(
self.buffer_size,
buffer::Role::Index,
memory::Usage::Dynamic,
memory::Bind::empty()
).expect("Count not create index buffer");
let font_bitmap = try!(match self.font_path {
Some(path) =>
BitmapFont::from_path(path, self.font_size, self.chars),
None => match self.font_data {
Some(data) => BitmapFont::from_bytes(data, self.font_size, self.chars),
None => Err(FontError::NoFont),
},
});
let font_texture = try!(create_texture_r8_static(
&mut self.factory,
font_bitmap.get_width(),
font_bitmap.get_height(),
font_bitmap.get_image(),
));
let sampler = self.factory.create_sampler(
texture::SamplerInfo::new(texture::FilterMethod::Bilinear,
texture::WrapMode::Clamp)
);
let shaders = try!(self.factory.create_shader_set(VERTEX_SRC, FRAGMENT_SRC));
Ok(Renderer {
factory: self.factory,
pso_map: HashMap::new(),
shaders: shaders,
vertex_data: Vec::new(),
vertex_buffer: vertex_buffer,
index_data: Vec::new(),
index_buffer: index_buffer,
font_bitmap: font_bitmap,
color: (font_texture, sampler),
})
}
pub fn unwrap(self) -> Renderer<R, F> {
self.build().unwrap()
}
}
impl<R: Resources, F: Factory<R>> Renderer<R, F> {
fn prepare_pso(&mut self, format: gfx::format::Format) -> Result<(), Error> {
Ok(if let Entry::Vacant(e) = self.pso_map.entry(format) {
let init = pipe::Init {
vbuf: (),
screen_size: "u_Screen_Size",
proj: "u_Proj",
color: "t_Color",
out_color: ("o_Color", format, gfx::state::ColorMask::all(), Some(gfx::preset::blend::ALPHA)),
};
let pso = try!(self.factory.create_pipeline_state(
&self.shaders,
gfx::Primitive::TriangleList,
gfx::state::Rasterizer::new_fill().with_cull_back(),
init
));
e.insert(pso);
})
}
pub fn add(&mut self, text: &str, pos: [i32; 2], color: [f32; 4]) {
self.add_generic(text, Ok(pos), color)
}
pub fn add_anchored(&mut self, text: &str, pos: [i32; 2], horizontal: HorizontalAnchor, vertical: VerticalAnchor, color: [f32; 4]) {
if horizontal == HorizontalAnchor::Left && vertical == VerticalAnchor::Top {
self.add_generic(text, Ok(pos), color);
return
}
let (width, height) = self.measure(text);
let x = match horizontal {
HorizontalAnchor::Left => pos[0],
HorizontalAnchor::Center => pos[0] - width / 2,
HorizontalAnchor::Right => pos[0] - width,
};
let y = match vertical {
VerticalAnchor::Top => pos[1],
VerticalAnchor::Center => pos[1] - height / 2,
VerticalAnchor::Bottom => pos[1] - height,
};
self.add_generic(text, Ok([x, y]), color)
}
pub fn add_at(&mut self, text: &str, pos: [f32; 3], color: [f32; 4]) {
self.add_generic(text, Err(pos), color)
}
fn add_generic(&mut self, text: &str, pos: Result<[i32; 2], [f32; 3]>, color: [f32; 4]) {
let (screen_pos, world_pos, screen_rel) = match pos {
Ok(screen_pos) => (screen_pos, [0.0, 0.0, 0.0], 1),
Err(world_pos) => ([0, 0], world_pos, 0),
};
let (mut x, y) = (screen_pos[0] as f32, screen_pos[1] as f32);
for ch in text.chars() {
let ch_info = match self.font_bitmap.find_char(ch) {
Some(info) => info,
None => continue,
};
let x_offset = x + ch_info.x_offset as f32;
let y_offset = y + ch_info.y_offset as f32;
let tex = ch_info.tex;
let index = self.vertex_data.len() as u32;
self.vertex_data.push(Vertex {
pos: [x_offset, y_offset],
tex: [tex[0], tex[1]],
world_pos: world_pos,
screen_rel: screen_rel,
color: color,
});
self.vertex_data.push(Vertex {
pos: [x_offset, y_offset + ch_info.height as f32],
tex: [tex[0], tex[1] + ch_info.tex_height],
world_pos: world_pos,
screen_rel: screen_rel,
color: color,
});
self.vertex_data.push(Vertex {
pos: [x_offset + ch_info.width as f32, y_offset + ch_info.height as f32],
tex: [tex[0] + ch_info.tex_width, tex[1] + ch_info.tex_height],
world_pos: world_pos,
screen_rel: screen_rel,
color: color,
});
self.vertex_data.push(Vertex {
pos: [x_offset + ch_info.width as f32, y_offset],
tex: [tex[0] + ch_info.tex_width, tex[1]],
world_pos: world_pos,
screen_rel: screen_rel,
color: color,
});
self.index_data.push(index + 0);
self.index_data.push(index + 1);
self.index_data.push(index + 3);
self.index_data.push(index + 3);
self.index_data.push(index + 1);
self.index_data.push(index + 2);
x += ch_info.x_advance as f32;
}
}
pub fn draw<C: CommandBuffer<R>, T: gfx::format::RenderFormat>(
&mut self,
encoder: &mut Encoder<R, C>,
target: &RenderTargetView<R, T>
) -> Result<(), Error> {
self.draw_at(encoder, target, DEFAULT_PROJECTION)
}
pub fn draw_at<C: CommandBuffer<R>, T: gfx::format::RenderFormat>(
&mut self,
encoder: &mut Encoder<R, C>,
target: &RenderTargetView<R, T>,
proj: [[f32; 4]; 4]
) -> Result<(), Error> {
use gfx::memory::{self, Typed};
use gfx::buffer;
let ver_len = self.vertex_data.len();
let ver_buf_len = self.vertex_buffer.len();
let ind_len = self.index_data.len();
let ind_buf_len = self.index_buffer.len();
if ver_len > ver_buf_len {
let len = grow_buffer_size(ver_buf_len, ver_len);
self.vertex_buffer = self.factory.create_buffer(
len, buffer::Role::Vertex, memory::Usage::Dynamic, memory::Bind::empty()
).expect("Could not reallocate vertex buffer");
}
if ind_len > ind_buf_len {
let len = grow_buffer_size(ind_buf_len, ind_len);
self.index_buffer = self.factory.create_buffer(
len, buffer::Role::Index, memory::Usage::Dynamic, memory::Bind::empty()
).expect("Could not reallocate index buffer");
}
try!(encoder.update_buffer(&self.vertex_buffer, &self.vertex_data, 0));
try!(encoder.update_buffer(&self.index_buffer, &self.index_data, 0));
let ni = self.index_data.len() as gfx::VertexCount;
let mut slice: gfx::Slice<R> = gfx::Slice {
base_vertex: 0,
start: 0,
end: self.index_buffer.len() as u32,
instances: None,
buffer: gfx::IndexBuffer::Index32(self.index_buffer.clone()),
};
slice.end = ni;
let data = pipe::Data {
vbuf: self.vertex_buffer.clone(),
proj: proj,
screen_size: {
let (w, h, _, _) = target.get_dimensions();
[w as f32, h as f32]
},
color: self.color.clone(),
out_color: target.raw().clone(),
};
try!(self.prepare_pso(T::get_format()));
let pso = &self.pso_map[&T::get_format()];
self.vertex_data.clear();
self.index_data.clear();
encoder.draw(&slice, pso, &data);
Ok(())
}
pub fn measure(&self, text: &str) -> (i32, i32) {
let mut width = 0;
let mut last_char = None;
for ch in text.chars() {
let ch_info = match self.font_bitmap.find_char(ch) {
Some(info) => info,
None => continue,
};
last_char = Some(ch_info);
width += ch_info.x_advance;
}
match last_char {
Some(info) => width += info.x_offset + info.width - info.x_advance,
None => (),
}
(width, self.font_bitmap.get_font_height() as i32)
}
}
fn grow_buffer_size(mut current_size: usize, desired_size: usize) -> usize {
if current_size < 1 {
current_size = 1;
}
while current_size < desired_size {
current_size *= 2;
}
current_size
}
fn create_texture_r8_static<R: Resources, F: Factory<R>>(
factory: &mut F,
width: u16,
height: u16,
data: &[u8],
) -> Result<gfx::handle::ShaderResourceView<R, f32>, CombinedError> {
let kind = texture::Kind::D2(width, height, texture::AaMode::Single);
let (_, texture_view) = try!(
factory.create_texture_immutable_u8::<(gfx::format::R8, gfx::format::Unorm)>(
kind, texture::Mipmap::Provided, &[data])
);
Ok(texture_view)
}
mod shader_structs {
extern crate gfx;
gfx_vertex_struct!( Vertex {
pos: [f32; 2] = "a_Pos",
tex: [f32; 2] = "a_TexCoord",
world_pos: [f32; 3] = "a_World_Pos",
screen_rel: i32 = "a_Screen_Rel",
color: [f32; 4] = "a_Color",
});
gfx_pipeline_base!( pipe {
vbuf: gfx::VertexBuffer<Vertex>,
screen_size: gfx::Global<[f32; 2]>,
proj: gfx::Global<[[f32; 4]; 4]>,
color: gfx::TextureSampler<f32>,
out_color: gfx::RawRenderTarget,
});
}
use shader_structs::{Vertex, pipe};
const VERTEX_SRC: &'static [u8] = b"
#version 150 core
in vec2 a_Pos;
in vec4 a_Color;
in vec2 a_TexCoord;
in vec4 a_World_Pos;
in int a_Screen_Rel;
out vec4 v_Color;
out vec2 v_TexCoord;
uniform vec2 u_Screen_Size;
uniform mat4 u_Proj;
void main() {
// On-screen offset from text origin.
vec2 v_Screen_Offset = vec2(
2 * a_Pos.x / u_Screen_Size.x - 1,
1 - 2 * a_Pos.y / u_Screen_Size.y
);
vec4 v_Screen_Pos = u_Proj * a_World_Pos;
vec2 v_World_Offset = a_Screen_Rel == 0
// Perspective divide to get normalized device coords.
? vec2 (
v_Screen_Pos.x / v_Screen_Pos.z + 1,
v_Screen_Pos.y / v_Screen_Pos.z - 1
) : vec2(0.0, 0.0);
v_Color = a_Color;
v_TexCoord = a_TexCoord;
gl_Position = vec4(v_World_Offset + v_Screen_Offset, 0.0, 1.0);
}
";
const FRAGMENT_SRC: &'static [u8] = b"
#version 150 core
in vec4 v_Color;
in vec2 v_TexCoord;
out vec4 o_Color;
uniform sampler2D t_Color;
void main() {
vec4 t_Font_Color = texture(t_Color, v_TexCoord);
o_Color = vec4(v_Color.rgb, t_Font_Color.r * v_Color.a);
}
";