extern crate conrod_core;
#[macro_use]
extern crate glium;
use conrod_core::{
Rect,
Scalar,
color,
image,
render,
text
};
#[derive(Clone, Debug)]
pub enum Command<'a> {
Draw(Draw<'a>),
Scizzor(glium::Rect),
}
#[derive(Clone, Debug)]
pub enum Draw<'a> {
Image(image::Id, &'a [Vertex]),
Plain(&'a [Vertex]),
}
enum PreparedCommand {
Image(image::Id, std::ops::Range<usize>),
Plain(std::ops::Range<usize>),
Scizzor(glium::Rect),
}
pub struct GlyphCache {
cache: text::GlyphCache<'static>,
texture: glium::texture::Texture2d,
}
pub struct Renderer {
program: glium::Program,
glyph_cache: GlyphCache,
commands: Vec<PreparedCommand>,
vertices: Vec<Vertex>,
}
pub struct Commands<'a> {
commands: std::slice::Iter<'a, PreparedCommand>,
vertices: &'a [Vertex],
}
#[derive(Debug)]
pub enum RendererCreationError {
Texture(glium::texture::TextureCreationError),
Program(glium::program::ProgramChooserCreationError),
}
#[derive(Debug)]
pub enum DrawError {
Buffer(glium::vertex::BufferCreationError),
Draw(glium::DrawError),
}
#[derive(Copy, Clone, Debug)]
pub struct Vertex {
pub mode: u32,
pub position: [f32; 2],
pub tex_coords: [f32; 2],
pub color: [f32; 4],
}
#[allow(unsafe_code)]
mod vertex_impl {
use super::Vertex;
implement_vertex!(Vertex, position, tex_coords, color, mode);
}
pub const MODE_TEXT: u32 = 0;
pub const MODE_IMAGE: u32 = 1;
pub const MODE_GEOMETRY: u32 = 2;
pub const VERTEX_SHADER_120: &'static str = "
#version 120
attribute vec2 position;
attribute vec2 tex_coords;
attribute vec4 color;
attribute float mode;
varying vec2 v_tex_coords;
varying vec4 v_color;
varying float v_mode;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
v_tex_coords = tex_coords;
v_color = color;
v_mode = mode;
}
";
pub const FRAGMENT_SHADER_120: &'static str = "
#version 120
uniform sampler2D tex;
varying vec2 v_tex_coords;
varying vec4 v_color;
varying float v_mode;
void main() {
// Text
if (v_mode == 0.0) {
gl_FragColor = v_color * vec4(1.0, 1.0, 1.0, texture2D(tex, v_tex_coords).r);
// Image
} else if (v_mode == 1.0) {
gl_FragColor = texture2D(tex, v_tex_coords);
// 2D Geometry
} else if (v_mode == 2.0) {
gl_FragColor = v_color;
}
}
";
pub const VERTEX_SHADER_140: &'static str = "
#version 140
in vec2 position;
in vec2 tex_coords;
in vec4 color;
in uint mode;
out vec2 v_tex_coords;
out vec4 v_color;
flat out uint v_mode;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
v_tex_coords = tex_coords;
v_color = color;
v_mode = mode;
}
";
pub const FRAGMENT_SHADER_140: &'static str = "
#version 140
uniform sampler2D tex;
in vec2 v_tex_coords;
in vec4 v_color;
flat in uint v_mode;
out vec4 f_color;
void main() {
// Text
if (v_mode == uint(0)) {
f_color = v_color * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r);
// Image
} else if (v_mode == uint(1)) {
f_color = texture(tex, v_tex_coords);
// 2D Geometry
} else if (v_mode == uint(2)) {
f_color = v_color;
}
}
";
pub const VERTEX_SHADER_300_ES: &'static str = "
#version 300 es
precision mediump float;
in vec2 position;
in vec2 tex_coords;
in vec4 color;
in uint mode;
out vec2 v_tex_coords;
out vec4 v_color;
flat out uint v_mode;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
v_tex_coords = tex_coords;
v_color = color;
v_mode = mode;
}
";
pub const FRAGMENT_SHADER_300_ES: &'static str = "
#version 300 es
precision mediump float;
uniform sampler2D tex;
in vec2 v_tex_coords;
in vec4 v_color;
flat in uint v_mode;
out vec4 f_color;
void main() {
// Text
if (v_mode == uint(0)) {
f_color = v_color * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r);
// Image
} else if (v_mode == uint(1)) {
f_color = texture(tex, v_tex_coords);
// 2D Geometry
} else if (v_mode == uint(2)) {
f_color = v_color;
}
}
";
pub trait TextureDimensions {
fn dimensions(&self) -> (u32, u32);
}
impl<T> TextureDimensions for T
where T: std::ops::Deref<Target=glium::texture::TextureAny>,
{
fn dimensions(&self) -> (u32, u32) {
(self.get_width(), self.get_height().unwrap_or(0))
}
}
pub fn program<F>(facade: &F) -> Result<glium::Program, glium::program::ProgramChooserCreationError>
where F: glium::backend::Facade,
{
program!(facade,
120 => { vertex: VERTEX_SHADER_120, fragment: FRAGMENT_SHADER_120 },
140 => { vertex: VERTEX_SHADER_140, fragment: FRAGMENT_SHADER_140 },
300 es => { vertex: VERTEX_SHADER_300_ES, fragment: FRAGMENT_SHADER_300_ES })
}
pub fn draw_parameters() -> glium::DrawParameters<'static> {
use glium::{Blend, BlendingFunction, LinearBlendingFactor};
let blend = Blend {
color: BlendingFunction::Addition {
source: LinearBlendingFactor::SourceAlpha,
destination: LinearBlendingFactor::OneMinusSourceAlpha,
},
alpha: BlendingFunction::Addition {
source: LinearBlendingFactor::One,
destination: LinearBlendingFactor::OneMinusSourceAlpha,
},
constant_value: (0.0, 0.0, 0.0, 0.0),
};
glium::DrawParameters { multisampling: true, blend: blend, ..Default::default() }
}
pub fn gamma_srgb_to_linear(c: [f32; 4]) -> [f32; 4] {
fn component(f: f32) -> f32 {
if f <= 0.04045 {
f / 12.92
} else {
((f + 0.055) / 1.055).powf(2.4)
}
}
[component(c[0]), component(c[1]), component(c[2]), c[3]]
}
pub fn text_texture_client_format(opengl_version: &glium::Version) -> glium::texture::ClientFormat {
match *opengl_version {
glium::Version(_, major, _) if major >= 3 => glium::texture::ClientFormat::U8,
_ => glium::texture::ClientFormat::U8U8U8,
}
}
pub fn text_texture_uncompressed_float_format(opengl_version: &glium::Version) -> glium::texture::UncompressedFloatFormat {
match *opengl_version {
glium::Version(_, major, _) if major >= 3 => glium::texture::UncompressedFloatFormat::U8,
_ => glium::texture::UncompressedFloatFormat::U8U8U8,
}
}
fn rusttype_glyph_cache(w: u32, h: u32) -> text::GlyphCache<'static> {
const SCALE_TOLERANCE: f32 = 0.1;
const POSITION_TOLERANCE: f32 = 0.1;
text::GlyphCache::builder()
.dimensions(w, h)
.scale_tolerance(SCALE_TOLERANCE)
.position_tolerance(POSITION_TOLERANCE)
.build()
}
fn glyph_cache_texture<F>(
facade: &F,
width: u32,
height: u32,
) -> Result<glium::texture::Texture2d, glium::texture::TextureCreationError>
where
F: glium::backend::Facade,
{
let context = facade.get_context();
let opengl_version = context.get_opengl_version();
let client_format = text_texture_client_format(opengl_version);
let uncompressed_float_format = text_texture_uncompressed_float_format(opengl_version);
let num_components = client_format.get_num_components() as u32;
let data_size = num_components as usize * width as usize * height as usize;
let data = std::borrow::Cow::Owned(vec![128u8; data_size]);
let grey_image = glium::texture::RawImage2d {
data: data,
width: width,
height: height,
format: client_format,
};
let format = uncompressed_float_format;
let no_mipmap = glium::texture::MipmapsOption::NoMipmap;
glium::texture::Texture2d::with_format(facade, grey_image, format, no_mipmap)
}
impl GlyphCache {
pub fn with_dimensions<F>(
facade: &F,
width: u32,
height: u32,
) -> Result<Self, glium::texture::TextureCreationError>
where
F: glium::backend::Facade,
{
let cache = rusttype_glyph_cache(width, height);
let texture = glyph_cache_texture(facade, width, height)?;
Ok(GlyphCache {
cache: cache,
texture: texture,
})
}
pub fn new<F>(facade: &F) -> Result<Self, glium::texture::TextureCreationError>
where
F: glium::backend::Facade,
{
let (w, h) = facade.get_context().get_framebuffer_dimensions();
Self::with_dimensions(facade, w, h)
}
pub fn texture(&self) -> &glium::texture::Texture2d {
&self.texture
}
}
pub trait Display {
fn opengl_version(&self) -> &glium::Version;
fn framebuffer_dimensions(&self) -> (u32, u32);
fn hidpi_factor(&self) -> f64;
}
impl Display for glium::Display {
fn opengl_version(&self) -> &glium::Version {
self.get_opengl_version()
}
fn framebuffer_dimensions(&self) -> (u32, u32) {
self.get_framebuffer_dimensions()
}
fn hidpi_factor(&self) -> f64 {
self.gl_window().window().scale_factor()
}
}
impl Renderer {
pub fn new<F>(facade: &F) -> Result<Self, RendererCreationError>
where F: glium::backend::Facade,
{
let glyph_cache = GlyphCache::new(facade)?;
Self::with_glyph_cache(facade, glyph_cache)
}
pub fn with_glyph_cache_dimensions<F>(
facade: &F,
width: u32,
height: u32,
) -> Result<Self, RendererCreationError>
where
F: glium::backend::Facade,
{
let glyph_cache = GlyphCache::with_dimensions(facade, width, height)?;
Self::with_glyph_cache(facade, glyph_cache)
}
fn with_glyph_cache<F>(facade: &F, gc: GlyphCache) -> Result<Self, RendererCreationError>
where
F: glium::backend::Facade,
{
let program = program(facade)?;
Ok(Renderer {
program: program,
glyph_cache: gc,
commands: Vec::new(),
vertices: Vec::new(),
})
}
pub fn commands(&self) -> Commands {
let Renderer { ref commands, ref vertices, .. } = *self;
Commands {
commands: commands.iter(),
vertices: vertices,
}
}
pub fn fill<D, P, T>(&mut self, display: &D, mut primitives: P, image_map: &image::Map<T>)
where
P: render::PrimitiveWalker,
D: Display,
T: TextureDimensions,
{
let Renderer { ref mut commands, ref mut vertices, ref mut glyph_cache, .. } = *self;
commands.clear();
vertices.clear();
let mut text_data_u8u8u8 = Vec::new();
let opengl_version = display.opengl_version();
let client_format = text_texture_client_format(opengl_version);
enum State {
Image { image_id: image::Id, start: usize },
Plain { start: usize },
}
let mut current_state = State::Plain { start: 0 };
macro_rules! switch_to_plain_state {
() => {
match current_state {
State::Plain { .. } => (),
State::Image { image_id, start } => {
commands.push(PreparedCommand::Image(image_id, start..vertices.len()));
current_state = State::Plain { start: vertices.len() };
},
}
};
}
let (screen_w, screen_h) = display.framebuffer_dimensions();
let (win_w, win_h) = (screen_w as Scalar, screen_h as Scalar);
let half_win_w = win_w / 2.0;
let half_win_h = win_h / 2.0;
let dpi_factor = display.hidpi_factor() as Scalar;
let vx = |x: Scalar| (x * dpi_factor / half_win_w) as f32;
let vy = |y: Scalar| (y * dpi_factor / half_win_h) as f32;
let mut current_scizzor = glium::Rect {
left: 0,
width: screen_w,
bottom: 0,
height: screen_h,
};
let rect_to_glium_rect = |rect: Rect| {
let (w, h) = rect.w_h();
let left = (rect.left() * dpi_factor + half_win_w).round() as u32;
let bottom = (rect.bottom() * dpi_factor + half_win_h).round() as u32;
let width = (w * dpi_factor).round() as u32;
let height = (h * dpi_factor).round() as u32;
glium::Rect {
left: std::cmp::max(left, 0),
bottom: std::cmp::max(bottom, 0),
width: std::cmp::min(width, screen_w),
height: std::cmp::min(height, screen_h),
}
};
while let Some(primitive) = primitives.next_primitive() {
let render::Primitive { kind, scizzor, rect, .. } = primitive;
let new_scizzor = rect_to_glium_rect(scizzor);
if new_scizzor != current_scizzor {
match current_state {
State::Plain { start } =>
commands.push(PreparedCommand::Plain(start..vertices.len())),
State::Image { image_id, start } =>
commands.push(PreparedCommand::Image(image_id, start..vertices.len())),
}
current_scizzor = new_scizzor;
commands.push(PreparedCommand::Scizzor(new_scizzor));
current_state = State::Plain { start: vertices.len() };
}
match kind {
render::PrimitiveKind::Rectangle { color } => {
switch_to_plain_state!();
let color = gamma_srgb_to_linear(color.to_fsa());
let (l, r, b, t) = rect.l_r_b_t();
let v = |x, y| {
Vertex {
position: [vx(x), vy(y)],
tex_coords: [0.0, 0.0],
color: color,
mode: MODE_GEOMETRY,
}
};
let mut push_v = |x, y| vertices.push(v(x, y));
push_v(l, t);
push_v(r, b);
push_v(l, b);
push_v(l, t);
push_v(r, b);
push_v(r, t);
},
render::PrimitiveKind::TrianglesSingleColor { color, triangles } => {
if triangles.is_empty() {
continue;
}
switch_to_plain_state!();
let color = gamma_srgb_to_linear(color.into());
let v = |p: [Scalar; 2]| {
Vertex {
position: [vx(p[0]), vy(p[1])],
tex_coords: [0.0, 0.0],
color: color,
mode: MODE_GEOMETRY,
}
};
for triangle in triangles {
vertices.push(v(triangle[0]));
vertices.push(v(triangle[1]));
vertices.push(v(triangle[2]));
}
},
render::PrimitiveKind::TrianglesMultiColor { triangles } => {
if triangles.is_empty() {
continue;
}
switch_to_plain_state!();
let v = |(p, c): ([Scalar; 2], color::Rgba)| {
Vertex {
position: [vx(p[0]), vy(p[1])],
tex_coords: [0.0, 0.0],
color: gamma_srgb_to_linear(c.into()),
mode: MODE_GEOMETRY,
}
};
for triangle in triangles {
vertices.push(v(triangle[0]));
vertices.push(v(triangle[1]));
vertices.push(v(triangle[2]));
}
},
render::PrimitiveKind::Text { color, text, font_id } => {
switch_to_plain_state!();
let positioned_glyphs = text.positioned_glyphs(dpi_factor as f32);
let GlyphCache { ref mut cache, ref mut texture } = *glyph_cache;
for glyph in positioned_glyphs.iter() {
cache.queue_glyph(font_id.index(), glyph.clone());
}
cache.cache_queued(|rect, data| {
let w = rect.width();
let h = rect.height();
let glium_rect = glium::Rect {
left: rect.min.x,
bottom: rect.min.y,
width: w,
height: h,
};
let data = match client_format {
glium::texture::ClientFormat::U8 => std::borrow::Cow::Borrowed(data),
glium::texture::ClientFormat::U8U8U8 => {
text_data_u8u8u8.clear();
for &b in data.iter() {
text_data_u8u8u8.push(b);
text_data_u8u8u8.push(b);
text_data_u8u8u8.push(b);
}
std::borrow::Cow::Borrowed(&text_data_u8u8u8[..])
},
_ => unreachable!(),
};
let image = glium::texture::RawImage2d {
data: data,
width: w,
height: h,
format: client_format,
};
texture.main_level().write(glium_rect, image);
}).unwrap();
let color = gamma_srgb_to_linear(color.to_fsa());
let cache_id = font_id.index();
let origin = text::rt::point(0.0, 0.0);
let to_gl_rect = |screen_rect: text::rt::Rect<i32>| text::rt::Rect {
min: origin
+ (text::rt::vector(screen_rect.min.x as f32 / screen_w as f32 - 0.5,
1.0 - screen_rect.min.y as f32 / screen_h as f32 - 0.5)) * 2.0,
max: origin
+ (text::rt::vector(screen_rect.max.x as f32 / screen_w as f32 - 0.5,
1.0 - screen_rect.max.y as f32 / screen_h as f32 - 0.5)) * 2.0
};
for g in positioned_glyphs {
if let Ok(Some((uv_rect, screen_rect))) = cache.rect_for(cache_id, g) {
let gl_rect = to_gl_rect(screen_rect);
let v = |p, t| Vertex {
position: p,
tex_coords: t,
color: color,
mode: MODE_TEXT,
};
let mut push_v = |p, t| vertices.push(v(p, t));
push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]);
push_v([gl_rect.min.x, gl_rect.min.y], [uv_rect.min.x, uv_rect.min.y]);
push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]);
push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]);
push_v([gl_rect.max.x, gl_rect.max.y], [uv_rect.max.x, uv_rect.max.y]);
push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]);
}
}
},
render::PrimitiveKind::Image { image_id, color, source_rect } => {
let new_image_id = image_id;
match current_state {
State::Image { image_id, .. } if image_id == new_image_id => (),
State::Plain { start } => {
commands.push(PreparedCommand::Plain(start..vertices.len()));
current_state = State::Image {
image_id: new_image_id,
start: vertices.len(),
};
},
State::Image { image_id, start } => {
commands.push(PreparedCommand::Image(image_id, start..vertices.len()));
current_state = State::Image {
image_id: new_image_id,
start: vertices.len(),
};
},
}
let color = color.unwrap_or(color::WHITE).to_fsa();
if let Some(image) = image_map.get(&image_id) {
let (image_w, image_h) = image.dimensions();
let (image_w, image_h) = (image_w as Scalar, image_h as Scalar);
let (uv_l, uv_r, uv_b, uv_t) = match source_rect {
Some(src_rect) => {
let (l, r, b, t) = src_rect.l_r_b_t();
((l / image_w) as f32,
(r / image_w) as f32,
(b / image_h) as f32,
(t / image_h) as f32)
},
None => (0.0, 1.0, 0.0, 1.0),
};
let v = |x, y, t| {
let x = (x * dpi_factor as Scalar / half_win_w) as f32;
let y = (y * dpi_factor as Scalar / half_win_h) as f32;
Vertex {
position: [x, y],
tex_coords: t,
color: color,
mode: MODE_IMAGE,
}
};
let mut push_v = |x, y, t| vertices.push(v(x, y, t));
let (l, r, b, t) = rect.l_r_b_t();
push_v(l, t, [uv_l, uv_t]);
push_v(r, b, [uv_r, uv_b]);
push_v(l, b, [uv_l, uv_b]);
push_v(l, t, [uv_l, uv_t]);
push_v(r, b, [uv_r, uv_b]);
push_v(r, t, [uv_r, uv_t]);
}
},
render::PrimitiveKind::Other(_) => (),
}
}
match current_state {
State::Plain { start } =>
commands.push(PreparedCommand::Plain(start..vertices.len())),
State::Image { image_id, start } =>
commands.push(PreparedCommand::Image(image_id, start..vertices.len())),
}
}
pub fn draw<F, S, T>(&self, facade: &F, surface: &mut S, image_map: &image::Map<T>)
-> Result<(), DrawError>
where F: glium::backend::Facade,
S: glium::Surface,
for<'a> glium::uniforms::Sampler<'a, T>: glium::uniforms::AsUniformValue,
{
let mut draw_params = draw_parameters();
let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList);
let uniforms = uniform! {
tex: self.glyph_cache.texture()
.sampled()
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest)
.minify_filter(glium::uniforms::MinifySamplerFilter::Linear)
};
const NUM_VERTICES_IN_TRIANGLE: usize = 3;
for command in self.commands() {
match command {
Command::Scizzor(scizzor) => draw_params.scissor = Some(scizzor),
Command::Draw(draw) => match draw {
Draw::Plain(slice) => if slice.len() >= NUM_VERTICES_IN_TRIANGLE {
let vertex_buffer = glium::VertexBuffer::new(facade, slice)?;
surface.draw(&vertex_buffer, no_indices, &self.program, &uniforms, &draw_params).unwrap();
},
Draw::Image(image_id, slice) => if slice.len() >= NUM_VERTICES_IN_TRIANGLE {
let vertex_buffer = glium::VertexBuffer::new(facade, slice).unwrap();
if let Some(image) = image_map.get(&image_id) {
let image_uniforms = uniform! {
tex: glium::uniforms::Sampler::new(image)
.wrap_function(glium::uniforms::SamplerWrapFunction::Clamp)
.magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest),
};
surface.draw(&vertex_buffer, no_indices, &self.program, &image_uniforms, &draw_params).unwrap();
}
},
}
}
}
Ok(())
}
}
impl<'a> Iterator for Commands<'a> {
type Item = Command<'a>;
fn next(&mut self) -> Option<Self::Item> {
let Commands { ref mut commands, ref vertices } = *self;
commands.next().map(|command| match *command {
PreparedCommand::Scizzor(scizzor) => Command::Scizzor(scizzor),
PreparedCommand::Plain(ref range) =>
Command::Draw(Draw::Plain(&vertices[range.clone()])),
PreparedCommand::Image(id, ref range) =>
Command::Draw(Draw::Image(id, &vertices[range.clone()])),
})
}
}
impl From<glium::texture::TextureCreationError> for RendererCreationError {
fn from(err: glium::texture::TextureCreationError) -> Self {
RendererCreationError::Texture(err)
}
}
impl From<glium::program::ProgramChooserCreationError> for RendererCreationError {
fn from(err: glium::program::ProgramChooserCreationError) -> Self {
RendererCreationError::Program(err)
}
}
impl std::error::Error for RendererCreationError {
fn description(&self) -> &str {
match *self {
RendererCreationError::Texture(ref e) => std::error::Error::description(e),
RendererCreationError::Program(ref e) => std::error::Error::description(e),
}
}
}
impl std::fmt::Display for RendererCreationError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match *self {
RendererCreationError::Texture(ref e) => std::fmt::Display::fmt(e, f),
RendererCreationError::Program(ref e) => std::fmt::Display::fmt(e, f),
}
}
}
impl From<glium::vertex::BufferCreationError> for DrawError {
fn from(err: glium::vertex::BufferCreationError) -> Self {
DrawError::Buffer(err)
}
}
impl From<glium::DrawError> for DrawError {
fn from(err: glium::DrawError) -> Self {
DrawError::Draw(err)
}
}
impl std::error::Error for DrawError {
fn description(&self) -> &str {
match *self {
DrawError::Buffer(ref e) => std::error::Error::description(e),
DrawError::Draw(ref e) => std::error::Error::description(e),
}
}
}
impl std::fmt::Display for DrawError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match *self {
DrawError::Buffer(ref e) => std::fmt::Display::fmt(e, f),
DrawError::Draw(ref e) => std::fmt::Display::fmt(e, f),
}
}
}