1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 14:20:04 -04:00

WIP: rust WebGL rendering

This commit is contained in:
Emil Ernerfeldt
2019-01-04 14:14:32 +01:00
parent bfa20be28e
commit a1ddef742d
22 changed files with 1346 additions and 202 deletions

View File

@@ -8,5 +8,6 @@ edition = "2018"
[dependencies]
# palette = "0.4"
rusttype = "0.7"
serde = "1"
serde_derive = "1"

Binary file not shown.

BIN
emgui/fonts/ProggyClean.ttf Normal file

Binary file not shown.

Binary file not shown.

218
emgui/src/font.rs Normal file
View File

@@ -0,0 +1,218 @@
#![allow(unused)] // TODO
use rusttype::{point, Scale};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlyphInfo {
/// X offset for nice rendering
offset_x: u16,
/// Y offset for nice rendering
offset_y: u16,
min_x: u16,
min_y: u16,
/// Inclusive.
max_x: u16,
/// Inclusive
max_y: u16,
}
/// Printable ascii characters [33, 126], which excludes 32 (space) and 127 (DEL)
const NUM_CHARS: usize = 94;
const FIRST_ASCII: usize = 33;
/// Inclusive
const LAST_ASCII: usize = 126;
#[derive(Clone)]
pub struct Font {
/// Maximum character height
scale: usize,
/// NUM_CHARS big
char_rects: Vec<GlyphInfo>,
atlas_width: usize,
atlas_height: usize,
atlas: Vec<u8>,
}
impl Font {
pub fn new(scale: usize) -> Font {
let font_data = include_bytes!("../fonts/ProggyClean.ttf");
let font = rusttype::Font::from_bytes(font_data as &[u8]).expect("Error constructing Font");
// println!(
// "font.v_metrics: {:?}",
// font.v_metrics(Scale::uniform(scale as f32))
// );
let glyphs: Vec<_> = Self::supported_characters()
.map(|c| {
let glyph = font.glyph(c);
assert_ne!(
glyph.id().0,
0,
"Failed to find a glyph for the character '{}'",
c
);
let glyph = glyph.scaled(Scale::uniform(scale as f32));
glyph.positioned(point(0.0, 0.0))
})
.collect();
// TODO: decide dynamically?
let atlas_width = 128;
let mut atlas_height = 8;
let mut atlas = vec![0; atlas_width * atlas_height];
// Make one white pixel for use for various stuff:
atlas[0] = 255;
let mut cursor_x = 1;
let mut cursor_y = 0;
let mut row_height = 1;
let mut char_rects = vec![];
for glyph in glyphs {
let bb = glyph
.pixel_bounding_box()
.expect("Failed to get pixel bounding box");
let glyph_width = bb.width() as usize;
let glyph_height = bb.height() as usize;
assert!(glyph_width >= 1);
assert!(glyph_height >= 1);
assert!(glyph_width <= atlas_width);
if cursor_x + glyph_width > atlas_width {
// New row:
cursor_x = 0;
cursor_y += row_height;
row_height = 0;
}
row_height = row_height.max(glyph_height);
while cursor_y + row_height >= atlas_height {
atlas_height *= 2;
}
if atlas_width * atlas_height > atlas.len() {
atlas.resize(atlas_width * atlas_height, 0);
}
glyph.draw(|x, y, v| {
if v > 0.0 {
let x = x as usize;
let y = y as usize;
let px = cursor_x + x as usize;
let py = cursor_y + y as usize;
atlas[py * atlas_width + px] = (v * 255.0).round() as u8;
}
});
let offset_y = scale as i32 + bb.min.y - 3; // TODO: use font.v_metrics
assert!(0 <= bb.min.x);
assert!(0 <= offset_y && offset_y < scale as i32);
char_rects.push(GlyphInfo {
offset_x: bb.min.x as u16,
offset_y: offset_y as u16,
min_x: cursor_x as u16,
min_y: cursor_y as u16,
max_x: (cursor_x + glyph_width - 1) as u16,
max_y: (cursor_y + glyph_height - 1) as u16,
});
cursor_x += glyph_width;
}
Font {
scale,
char_rects,
atlas_width,
atlas_height,
atlas,
}
}
pub fn supported_characters() -> impl Iterator<Item = char> {
(FIRST_ASCII..=LAST_ASCII).map(|c| c as u8 as char)
}
pub fn texture(&self) -> (usize, usize, &[u8]) {
(self.atlas_width, self.atlas_height, &self.atlas)
}
pub fn pixel(&self, x: u16, y: u16) -> u8 {
let x = x as usize;
let y = y as usize;
assert!(x < self.atlas_width);
assert!(y < self.atlas_height);
self.atlas[y * self.atlas_width + x]
}
pub fn glyph_info(&self, c: char) -> Option<GlyphInfo> {
let c = c as usize;
if FIRST_ASCII <= c && c <= LAST_ASCII {
Some(self.char_rects[c - FIRST_ASCII])
} else {
None
}
}
pub fn debug_print_atlas_ascii_art(&self) {
for y in 0..self.atlas_height {
println!(
"{}",
as_ascii(&self.atlas[y * self.atlas_width..(y + 1) * self.atlas_width])
);
}
}
pub fn debug_print_all_chars(&self) {
let max_width = 160;
let mut pixel_rows = vec![vec![0; max_width]; self.scale];
let mut cursor_x = 0;
let mut cursor_y = 0;
for c in Self::supported_characters() {
if let Some(glyph_info) = self.glyph_info(c) {
for x in glyph_info.min_x..=glyph_info.max_x {
for y in glyph_info.min_y..=glyph_info.max_y {
let pixel = self.pixel(x, y);
let rx = glyph_info.offset_x + x - glyph_info.min_x;
let ry = glyph_info.offset_y + y - glyph_info.min_y;
pixel_rows[cursor_y + ry as usize][cursor_x + rx as usize] = pixel;
}
}
cursor_x += 7; // TODO
if cursor_x + 7 >= max_width {
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
for row in pixel_rows {
println!("{}", as_ascii(&row));
}
pixel_rows = vec![vec![0; max_width]; self.scale];
cursor_x = 0;
}
}
}
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
}
}
fn as_ascii(pixels: &[u8]) -> String {
pixels
.iter()
.map(|pixel| if *pixel == 0 { ' ' } else { 'X' })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn font_test() {
let font = Font::new(13);
font.debug_print_atlas_ascii_art();
font.debug_print_all_chars();
panic!();
}
}

View File

@@ -1,16 +1,24 @@
#![deny(warnings)]
extern crate rusttype;
extern crate serde;
#[macro_use] // TODO: get rid of this
extern crate serde_derive;
mod emgui;
mod font;
mod layout;
pub mod math;
mod painter;
mod style;
pub mod types;
pub use crate::{
emgui::Emgui, layout::Layout, layout::LayoutOptions, style::Style, types::RawInput,
emgui::Emgui,
layout::Layout,
layout::LayoutOptions,
painter::{Frame, Painter, Vertex},
style::Style,
types::RawInput,
};

88
emgui/src/painter.rs Normal file
View File

@@ -0,0 +1,88 @@
/// Outputs render info in a format suitable for e.g. OpenGL.
use crate::{
font::Font,
types::{Color, PaintCmd},
};
#[derive(Clone, Copy, Debug, Default)]
pub struct Vertex {
/// Pixel coordinated
pub x: f32,
/// Pixel coordinated
pub y: f32,
/// Texel indices into the texture
pub u: u16,
/// Texel indices into the texture
pub v: u16,
/// sRGBA
pub color: Color,
}
#[derive(Clone, Debug, Default)]
pub struct Frame {
pub clear_color: Option<Color>,
/// One big triangle strip
pub indices: Vec<u32>,
pub vertices: Vec<Vertex>,
}
#[derive(Clone)]
pub struct Painter {
font: Font,
}
impl Painter {
pub fn new() -> Painter {
Painter {
font: Font::new(13),
}
}
/// 8-bit row-major font atlas texture, (width, height, pixels).
pub fn texture(&self) -> (usize, usize, &[u8]) {
self.font.texture()
}
pub fn paint(&self, commands: &[PaintCmd]) -> Frame {
let mut frame = Frame::default();
for cmd in commands {
match cmd {
PaintCmd::Circle { .. } => {} // TODO
PaintCmd::Clear { fill_color } => {
frame.clear_color = Some(*fill_color);
}
PaintCmd::Line { .. } => {} // TODO
PaintCmd::Rect {
pos,
size,
fill_color,
..
} => {
// TODO: rounded corners, colors etc.
let idx = frame.vertices.len() as u32;
frame.indices.push(idx + 0);
frame.indices.push(idx + 0);
frame.indices.push(idx + 1);
frame.indices.push(idx + 2);
frame.indices.push(idx + 3);
frame.indices.push(idx + 3);
let vert = |x, y| Vertex {
x,
y,
u: 0,
v: 0,
color: fill_color.unwrap_or(Color::WHITE),
};
frame.vertices.push(vert(pos.x, pos.y));
frame.vertices.push(vert(pos.x + size.x, pos.y));
frame.vertices.push(vert(pos.x, pos.y + size.y));
frame.vertices.push(vert(pos.x + size.x, pos.y + size.y));
}
PaintCmd::Text { .. } => {} // TODO
}
}
frame
}
}

View File

@@ -57,7 +57,11 @@ pub struct Color {
pub a: u8,
}
pub fn srgba(r: u8, g: u8, b: u8, a: u8) -> Color {
impl Color {
pub const WHITE: Color = srgba(255, 255, 255, 255);
}
pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Color {
Color { r, g, b, a }
}