use crate::{color, image, render};
use crate::text::{self, rt};
use crate::{Rect, Scalar};
use std::{fmt, ops};
pub trait ImageDimensions {
fn dimensions(&self) -> [u32; 2];
}
#[derive(Debug)]
pub struct Mesh {
glyph_cache: GlyphCache,
glyph_cache_pixel_buffer: Vec<u8>,
commands: Vec<PreparedCommand>,
vertices: Vec<Vertex>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Scizzor {
pub top_left: [i32; 2],
pub dimensions: [u32; 2],
}
#[derive(Clone, Debug)]
pub enum Command {
Draw(Draw),
Scizzor(Scizzor),
}
pub struct Commands<'a> {
commands: std::slice::Iter<'a, PreparedCommand>,
}
#[derive(Clone, Debug)]
pub enum Draw {
Image(image::Id, std::ops::Range<usize>),
Plain(std::ops::Range<usize>),
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Vertex {
pub position: [f32; 2],
pub tex_coords: [f32; 2],
pub rgba: [f32; 4],
pub mode: u32,
}
#[allow(missing_copy_implementations)]
pub struct Fill {
pub glyph_cache_requires_upload: bool,
}
struct GlyphCache(text::GlyphCache<'static>);
#[derive(Debug)]
enum PreparedCommand {
Image(image::Id, std::ops::Range<usize>),
Plain(std::ops::Range<usize>),
Scizzor(Scizzor),
}
pub const MODE_TEXT: u32 = 0;
pub const MODE_IMAGE: u32 = 1;
pub const MODE_GEOMETRY: u32 = 2;
pub const DEFAULT_GLYPH_CACHE_DIMS: [u32; 2] = [1_024; 2];
impl Mesh {
pub fn new() -> Self {
Self::with_glyph_cache_dimensions(DEFAULT_GLYPH_CACHE_DIMS)
}
pub fn with_glyph_cache_dimensions(glyph_cache_dims: [u32; 2]) -> Self {
const SCALE_TOLERANCE: f32 = 0.1;
const POSITION_TOLERANCE: f32 = 0.1;
let [gc_width, gc_height] = glyph_cache_dims;
let glyph_cache = text::GlyphCache::builder()
.dimensions(gc_width, gc_height)
.scale_tolerance(SCALE_TOLERANCE)
.position_tolerance(POSITION_TOLERANCE)
.build()
.into();
let glyph_cache_pixel_buffer = vec![0u8; gc_width as usize * gc_height as usize];
let commands = vec![];
let vertices = vec![];
Mesh {
glyph_cache,
glyph_cache_pixel_buffer,
commands,
vertices,
}
}
pub fn fill<P, I>(
&mut self,
viewport: Rect,
dpi_factor: f64,
image_map: &image::Map<I>,
mut primitives: P,
) -> Result<Fill, rt::gpu_cache::CacheWriteErr>
where
P: render::PrimitiveWalker,
I: ImageDimensions,
{
let Mesh {
ref mut glyph_cache,
ref mut glyph_cache_pixel_buffer,
ref mut commands,
ref mut vertices,
} = *self;
commands.clear();
vertices.clear();
enum State {
Image { image_id: image::Id, start: usize },
Plain { start: usize },
}
let mut current_state = State::Plain { start: 0 };
let mut glyph_cache_requires_upload = false;
let (viewport_w, viewport_h) = viewport.w_h();
let half_viewport_w = viewport_w / 2.0;
let half_viewport_h = viewport_h / 2.0;
let (glyph_cache_w, _) = glyph_cache.dimensions();
let glyph_cache_w = glyph_cache_w as usize;
let vx = |x: Scalar| (x * dpi_factor / half_viewport_w) as f32;
let vy = |y: Scalar| -1.0 * (y * dpi_factor / half_viewport_h) as f32;
let rect_to_scizzor = |rect: Rect| {
let (w, h) = rect.w_h();
let left = (rect.left() * dpi_factor + half_viewport_w).round() as i32;
let top = (rect.top() * dpi_factor - half_viewport_h).round().abs() as i32;
let width = (w * dpi_factor).round() as u32;
let height = (h * dpi_factor).round() as u32;
Scizzor {
top_left: [left.max(0), top.max(0)],
dimensions: [width.min(viewport_w as u32), height.min(viewport_h as u32)],
}
};
let mut current_scizzor = rect_to_scizzor(viewport);
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(),
};
}
}
};
}
while let Some(primitive) = primitives.next_primitive() {
let render::Primitive {
kind,
scizzor,
rect,
..
} = primitive;
let new_scizzor = rect_to_scizzor(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],
rgba: 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],
rgba: 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],
rgba: 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);
for glyph in positioned_glyphs {
glyph_cache.queue_glyph(font_id.index(), glyph.clone());
}
glyph_cache.cache_queued(|rect, data| {
let width = (rect.max.x - rect.min.x) as usize;
let height = (rect.max.y - rect.min.y) as usize;
let mut dst_ix = rect.min.y as usize * glyph_cache_w + rect.min.x as usize;
let mut src_ix = 0;
for _ in 0..height {
let dst_range = dst_ix..dst_ix + width;
let src_range = src_ix..src_ix + width;
let dst_slice = &mut glyph_cache_pixel_buffer[dst_range];
let src_slice = &data[src_range];
dst_slice.copy_from_slice(src_slice);
dst_ix += glyph_cache_w;
src_ix += width;
}
glyph_cache_requires_upload = true;
})?;
let color = gamma_srgb_to_linear(color.to_fsa());
let cache_id = font_id.index();
let origin = rt::point(0.0, 0.0);
let to_vk_rect = |screen_rect: rt::Rect<i32>| rt::Rect {
min: origin
+ (rt::vector(
screen_rect.min.x as f32 / viewport_w as f32 - 0.5,
screen_rect.min.y as f32 / viewport_h as f32 - 0.5,
)) * 2.0,
max: origin
+ (rt::vector(
screen_rect.max.x as f32 / viewport_w as f32 - 0.5,
screen_rect.max.y as f32 / viewport_h as f32 - 0.5,
)) * 2.0,
};
for g in positioned_glyphs {
if let Ok(Some((uv_rect, screen_rect))) = glyph_cache.rect_for(cache_id, g)
{
let vk_rect = to_vk_rect(screen_rect);
let v = |p, t| Vertex {
position: p,
tex_coords: t,
rgba: color,
mode: MODE_TEXT,
};
let mut push_v = |p, t| vertices.push(v(p, t));
push_v(
[vk_rect.min.x, vk_rect.max.y],
[uv_rect.min.x, uv_rect.max.y],
);
push_v(
[vk_rect.min.x, vk_rect.min.y],
[uv_rect.min.x, uv_rect.min.y],
);
push_v(
[vk_rect.max.x, vk_rect.min.y],
[uv_rect.max.x, uv_rect.min.y],
);
push_v(
[vk_rect.max.x, vk_rect.min.y],
[uv_rect.max.x, uv_rect.min.y],
);
push_v(
[vk_rect.max.x, vk_rect.max.y],
[uv_rect.max.x, uv_rect.max.y],
);
push_v(
[vk_rect.min.x, vk_rect.max.y],
[uv_rect.min.x, uv_rect.max.y],
);
}
}
}
render::PrimitiveKind::Image {
image_id,
color,
source_rect,
} => {
let image_ref = match image_map.get(&image_id) {
None => continue,
Some(img) => img,
};
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();
let [image_w, image_h] = image_ref.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,
1.0 - (b / image_h) as f32,
1.0 - (t / image_h) as f32,
)
}
None => (0.0, 1.0, 1.0, 0.0),
};
let v = |x, y, t| {
let x = (x * dpi_factor / half_viewport_w) as f32;
let y = -((y * dpi_factor / half_viewport_h) as f32);
Vertex {
position: [x, y],
tex_coords: t,
rgba: 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()))
}
}
let fill = Fill {
glyph_cache_requires_upload,
};
Ok(fill)
}
pub fn glyph_cache(&self) -> &text::GlyphCache {
&self.glyph_cache.0
}
pub fn glyph_cache_pixel_buffer(&self) -> &[u8] {
&self.glyph_cache_pixel_buffer
}
pub fn commands(&self) -> Commands {
let Mesh {
ref commands,
..
} = *self;
Commands {
commands: commands.iter(),
}
}
pub fn vertices(&self) -> &[Vertex] {
&self.vertices
}
}
impl<'a> Iterator for Commands<'a> {
type Item = Command;
fn next(&mut self) -> Option<Self::Item> {
let Commands {
ref mut commands,
} = *self;
commands.next().map(|command| match *command {
PreparedCommand::Scizzor(scizzor) => Command::Scizzor(scizzor),
PreparedCommand::Plain(ref range) => {
Command::Draw(Draw::Plain(range.clone()))
}
PreparedCommand::Image(id, ref range) => {
Command::Draw(Draw::Image(id, range.clone()))
}
})
}
}
impl ops::Deref for GlyphCache {
type Target = text::GlyphCache<'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ops::DerefMut for GlyphCache {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl fmt::Debug for GlyphCache {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "GlyphCache")
}
}
impl From<text::GlyphCache<'static>> for GlyphCache {
fn from(gc: text::GlyphCache<'static>) -> Self {
GlyphCache(gc)
}
}
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]]
}