mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
wallet: for labels, create a texture atlas so quads can be batched as a single draw call
This commit is contained in:
@@ -294,7 +294,7 @@ impl App {
|
||||
prop.set_f32(2, 800.).unwrap();
|
||||
prop.set_f32(3, 200.).unwrap();
|
||||
node.set_property_f32("baseline", 40.).unwrap();
|
||||
node.set_property_f32("font_size", 20.).unwrap();
|
||||
node.set_property_f32("font_size", 100.).unwrap();
|
||||
//node.set_property_str("text", "anon1🍆").unwrap();
|
||||
node.set_property_str("text", "anon1").unwrap();
|
||||
let prop = node.get_property("color").unwrap();
|
||||
|
||||
@@ -36,6 +36,20 @@ pub struct Point {
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn unpack(&self) -> (f32, f32) {
|
||||
(self.x, self.y)
|
||||
}
|
||||
|
||||
pub fn offset(&self, off_x: f32, off_y: f32) -> Self {
|
||||
Self { x: self.x + off_x, y: self.y + off_y }
|
||||
}
|
||||
|
||||
pub fn to_rect(&self, w: f32, h: f32) -> Rectangle {
|
||||
Rectangle { x: self.x, y: self.y, w, h }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Rectangle {
|
||||
pub x: f32,
|
||||
@@ -45,6 +59,10 @@ pub struct Rectangle {
|
||||
}
|
||||
|
||||
impl Rectangle {
|
||||
pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
|
||||
Self { x, y, w, h }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self { x: 0., y: 0., w: 0., h: 0. }
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ mod expr;
|
||||
//mod gfx;
|
||||
mod gfx2;
|
||||
mod keysym;
|
||||
mod mesh;
|
||||
mod net;
|
||||
//mod plugin;
|
||||
mod prop;
|
||||
|
||||
84
bin/darkwallet/src/mesh.rs
Normal file
84
bin/darkwallet/src/mesh.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::{
|
||||
error::Result,
|
||||
gfx2::{Point, Rectangle, RenderApi, Vertex},
|
||||
};
|
||||
use miniquad::BufferId;
|
||||
|
||||
pub type Color = [f32; 4];
|
||||
|
||||
pub const COLOR_RED: Color = [1., 0., 0., 1.];
|
||||
pub const COLOR_DARKGREY: Color = [0.2, 0.2, 0.2, 1.];
|
||||
pub const COLOR_GREEN: Color = [0., 1., 0., 1.];
|
||||
pub const COLOR_BLUE: Color = [0., 0., 1., 1.];
|
||||
pub const COLOR_WHITE: Color = [1., 1., 1., 1.];
|
||||
|
||||
pub struct MeshBuilder {
|
||||
verts: Vec<Vertex>,
|
||||
indices: Vec<u16>,
|
||||
}
|
||||
|
||||
impl MeshBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self { verts: vec![], indices: vec![] }
|
||||
}
|
||||
|
||||
pub fn append(&mut self, mut verts: Vec<Vertex>, indices: Vec<u16>) {
|
||||
let mut indices = indices.into_iter().map(|i| i + self.verts.len() as u16).collect();
|
||||
self.verts.append(&mut verts);
|
||||
self.indices.append(&mut indices);
|
||||
}
|
||||
|
||||
pub fn draw_box(&mut self, obj: &Rectangle, color: Color, uv: &Rectangle) {
|
||||
let (x1, y1) = obj.top_left().unpack();
|
||||
let (x2, y2) = obj.bottom_right().unpack();
|
||||
|
||||
let (u1, v1) = uv.top_left().unpack();
|
||||
let (u2, v2) = uv.bottom_right().unpack();
|
||||
|
||||
let verts = vec![
|
||||
// top left
|
||||
Vertex { pos: [x1, y1], color, uv: [u1, v1] },
|
||||
// top right
|
||||
Vertex { pos: [x2, y1], color, uv: [u2, v1] },
|
||||
// bottom left
|
||||
Vertex { pos: [x1, y2], color, uv: [u1, v2] },
|
||||
// bottom right
|
||||
Vertex { pos: [x2, y2], color, uv: [u2, v2] },
|
||||
];
|
||||
let indices = vec![0, 2, 1, 1, 2, 3];
|
||||
|
||||
self.append(verts, indices);
|
||||
}
|
||||
|
||||
pub fn draw_outline(&mut self, obj: &Rectangle, color: Color, thickness: f32) {
|
||||
let uv = Rectangle { x: 0., y: 0., w: 0., h: 0. };
|
||||
|
||||
let (x1, y1) = obj.top_left().unpack();
|
||||
let (dist_x, dist_y) = (obj.w, obj.h);
|
||||
let (x2, y2) = obj.bottom_right().unpack();
|
||||
|
||||
// top
|
||||
self.draw_box(&Rectangle::new(x1, y1, dist_x, thickness), color, &uv);
|
||||
// left
|
||||
self.draw_box(&Rectangle::new(x1, y1, thickness, dist_y), color, &uv);
|
||||
// right
|
||||
self.draw_box(&Rectangle::new(x2 - thickness, y1, thickness, dist_y), color, &uv);
|
||||
// bottom
|
||||
self.draw_box(&Rectangle::new(x1, y2 - thickness, dist_x, thickness), color, &uv);
|
||||
}
|
||||
|
||||
// Needed by OpenGL
|
||||
pub fn num_elements(&self) -> i32 {
|
||||
self.indices.len() as i32
|
||||
}
|
||||
|
||||
pub async fn alloc(self, render_api: &RenderApi) -> Result<(BufferId, BufferId)> {
|
||||
debug!(target: "mesh", "allocating {} verts:", self.verts.len());
|
||||
for vert in &self.verts {
|
||||
debug!(target: "mesh", " {:?}", vert);
|
||||
}
|
||||
let vertex_buffer = render_api.new_vertex_buffer(self.verts).await?;
|
||||
let index_buffer = render_api.new_index_buffer(self.indices).await?;
|
||||
Ok((vertex_buffer, index_buffer))
|
||||
}
|
||||
}
|
||||
@@ -49,56 +49,157 @@ pub async fn make_texture_atlas(
|
||||
font_size: f32,
|
||||
glyphs: &Vec<Glyph>,
|
||||
) -> Result<RenderedAtlas> {
|
||||
// First compute total size of the atlas
|
||||
let mut total_width = ATLAS_GAP;
|
||||
let mut total_height = ATLAS_GAP;
|
||||
for glyph in glyphs {
|
||||
|
||||
// Glyph IDs already rendered so we don't do it twice
|
||||
let mut rendered = vec![];
|
||||
|
||||
for (idx, glyph) in glyphs.iter().enumerate() {
|
||||
let sprite = &glyph.sprite;
|
||||
assert_eq!(sprite.bmp.len(), 4 * sprite.bmp_width * sprite.bmp_height);
|
||||
|
||||
// Already done this one so skip
|
||||
if rendered.contains(&glyph.glyph_id) {
|
||||
continue
|
||||
}
|
||||
rendered.push(glyph.glyph_id);
|
||||
|
||||
total_width += sprite.bmp_width + ATLAS_GAP;
|
||||
total_height = std::cmp::max(total_height, sprite.bmp_height);
|
||||
}
|
||||
total_width += ATLAS_GAP;
|
||||
total_height += 2 * ATLAS_GAP;
|
||||
|
||||
// Allocate the big texture now
|
||||
let mut atlas_bmp = vec![0; 4 * total_width * total_height];
|
||||
// For debug lines we want a single white pixel.
|
||||
atlas_bmp[0] = 255;
|
||||
atlas_bmp[1] = 255;
|
||||
atlas_bmp[2] = 255;
|
||||
atlas_bmp[3] = 255;
|
||||
|
||||
// Calculate dimensions of final product first
|
||||
let mut current_x = ATLAS_GAP;
|
||||
let mut uv_rects = vec![];
|
||||
let mut rendered_glyphs: Vec<u32> = vec![];
|
||||
let mut uv_rects: Vec<Rectangle> = vec![];
|
||||
|
||||
for glyph in glyphs {
|
||||
for (idx, glyph) in glyphs.iter().enumerate() {
|
||||
let sprite = &glyph.sprite;
|
||||
|
||||
for i in 0..sprite.bmp_height {
|
||||
for j in 0..sprite.bmp_width {
|
||||
let off_dest = 4 * ((i + ATLAS_GAP) * total_width + j + current_x + ATLAS_GAP);
|
||||
let off_src = 4 * (i * sprite.bmp_width + j);
|
||||
atlas_bmp[off_dest] = sprite.bmp[off_src];
|
||||
atlas_bmp[off_dest + 1] = sprite.bmp[off_src + 1];
|
||||
atlas_bmp[off_dest + 2] = sprite.bmp[off_src + 2];
|
||||
atlas_bmp[off_dest + 3] = sprite.bmp[off_src + 3];
|
||||
// Did we already rendered this glyph?
|
||||
// If so just copy the UV rect from before.
|
||||
let mut uv_rect = None;
|
||||
for (rendered_glyph_id, rendered_uv_rect) in rendered_glyphs.iter().zip(uv_rects.iter()) {
|
||||
if *rendered_glyph_id == glyph.glyph_id {
|
||||
uv_rect = Some(rendered_uv_rect.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Compute UV coords
|
||||
let uv_rect = Rectangle {
|
||||
x: current_x as f32 / total_width as f32,
|
||||
y: 0.,
|
||||
w: sprite.bmp_width as f32 / total_width as f32,
|
||||
h: sprite.bmp_height as f32 / total_height as f32,
|
||||
};
|
||||
uv_rects.push(uv_rect);
|
||||
let uv_rect = match uv_rect {
|
||||
Some(uv_rect) => uv_rect,
|
||||
// Allocating a new glyph sprite in the atlas
|
||||
None => {
|
||||
copy_image(sprite, &mut atlas_bmp, total_width, current_x);
|
||||
|
||||
current_x += sprite.bmp_width + ATLAS_GAP;
|
||||
// Compute UV coords
|
||||
let uv_rect = Rectangle {
|
||||
x: current_x as f32 / total_width as f32,
|
||||
y: 0.,
|
||||
w: sprite.bmp_width as f32 / total_width as f32,
|
||||
h: sprite.bmp_height as f32 / total_height as f32,
|
||||
};
|
||||
|
||||
current_x += sprite.bmp_width + ATLAS_GAP;
|
||||
|
||||
uv_rect
|
||||
}
|
||||
};
|
||||
|
||||
rendered_glyphs.push(glyph.glyph_id);
|
||||
uv_rects.push(uv_rect);
|
||||
}
|
||||
|
||||
// Finally allocate the texture
|
||||
let texture_id =
|
||||
render_api.new_texture(total_width as u16, total_height as u16, atlas_bmp).await?;
|
||||
|
||||
Ok(RenderedAtlas { uv_rects, texture_id })
|
||||
}
|
||||
|
||||
fn copy_image(sprite: &Sprite, atlas_bmp: &mut Vec<u8>, total_width: usize, current_x: usize) {
|
||||
for i in 0..sprite.bmp_height {
|
||||
for j in 0..sprite.bmp_width {
|
||||
let off_dest = 4 * ((i + ATLAS_GAP) * total_width + j + current_x + ATLAS_GAP);
|
||||
let off_src = 4 * (i * sprite.bmp_width + j);
|
||||
atlas_bmp[off_dest] = sprite.bmp[off_src];
|
||||
atlas_bmp[off_dest + 1] = sprite.bmp[off_src + 1];
|
||||
atlas_bmp[off_dest + 2] = sprite.bmp[off_src + 2];
|
||||
atlas_bmp[off_dest + 3] = sprite.bmp[off_src + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlyphPositionIter<'a> {
|
||||
font_size: f32,
|
||||
glyphs: &'a Vec<Glyph>,
|
||||
current_x: f32,
|
||||
current_y: f32,
|
||||
i: usize,
|
||||
}
|
||||
|
||||
impl<'a> GlyphPositionIter<'a> {
|
||||
pub fn new(font_size: f32, glyphs: &'a Vec<Glyph>, baseline_y: f32) -> Self {
|
||||
Self { font_size, glyphs, current_x: 0., current_y: baseline_y, i: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for GlyphPositionIter<'a> {
|
||||
type Item = Rectangle;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
assert!(self.i <= self.glyphs.len());
|
||||
if self.i == self.glyphs.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let glyph = &self.glyphs[self.i];
|
||||
let sprite = &glyph.sprite;
|
||||
|
||||
let rect = if sprite.has_fixed_sizes {
|
||||
// Downscale by height
|
||||
let w = (sprite.bmp_width as f32 * self.font_size) / sprite.bmp_height as f32;
|
||||
let h = self.font_size;
|
||||
|
||||
let x = self.current_x;
|
||||
let y = self.current_y - h;
|
||||
|
||||
self.current_x += w;
|
||||
|
||||
Rectangle { x, y, w, h }
|
||||
} else {
|
||||
let (w, h) = (sprite.bmp_width as f32, sprite.bmp_height as f32);
|
||||
|
||||
let off_x = glyph.x_offset as f32 / 64.;
|
||||
let off_y = glyph.y_offset as f32 / 64.;
|
||||
|
||||
let x = self.current_x + off_x + sprite.bearing_x;
|
||||
let y = self.current_y - off_y - sprite.bearing_y;
|
||||
|
||||
let x_advance = glyph.x_advance;
|
||||
let y_advance = glyph.y_advance;
|
||||
self.current_x += x_advance;
|
||||
self.current_y += y_advance;
|
||||
|
||||
Rectangle { x, y, w, h }
|
||||
};
|
||||
|
||||
self.i += 1;
|
||||
Some(rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextShaper {
|
||||
font_faces: Mutex<FtFaces>,
|
||||
cache: Mutex<TextShaperCache>,
|
||||
@@ -434,16 +535,16 @@ struct CacheKey {
|
||||
face_idx: usize,
|
||||
}
|
||||
|
||||
type SpritePtr = Arc<Sprite>;
|
||||
pub type SpritePtr = Arc<Sprite>;
|
||||
|
||||
struct Sprite {
|
||||
pub struct Sprite {
|
||||
bmp: Vec<u8>,
|
||||
bmp_width: usize,
|
||||
bmp_height: usize,
|
||||
pub bmp_width: usize,
|
||||
pub bmp_height: usize,
|
||||
|
||||
bearing_x: f32,
|
||||
bearing_y: f32,
|
||||
has_fixed_sizes: bool,
|
||||
pub bearing_x: f32,
|
||||
pub bearing_y: f32,
|
||||
pub has_fixed_sizes: bool,
|
||||
}
|
||||
|
||||
pub struct Glyph {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use async_lock::Mutex;
|
||||
use miniquad::{BufferId, TextureId};
|
||||
use rand::{rngs::OsRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use crate::{
|
||||
gfx2::{DrawCall, DrawInstruction, DrawMesh, Rectangle, RenderApiPtr, Vertex},
|
||||
mesh::{MeshBuilder, COLOR_BLUE, COLOR_WHITE},
|
||||
prop::{PropertyPtr, PropertyUint32},
|
||||
scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
|
||||
text2::{self, Glyph, RenderedAtlas, TextShaperPtr},
|
||||
text2::{self, Glyph, GlyphPositionIter, RenderedAtlas, SpritePtr, TextShaperPtr},
|
||||
};
|
||||
|
||||
use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
|
||||
@@ -19,10 +21,11 @@ pub struct Text {
|
||||
text_shaper: TextShaperPtr,
|
||||
tasks: Vec<smol::Task<()>>,
|
||||
|
||||
glyphs: Mutex<(Vec<Glyph>, RenderedAtlas)>,
|
||||
glyph_sprites: Mutex<Vec<SpritePtr>>,
|
||||
|
||||
vertex_buffer: miniquad::BufferId,
|
||||
index_buffer: miniquad::BufferId,
|
||||
texture_id: TextureId,
|
||||
vertex_buffer: BufferId,
|
||||
index_buffer: BufferId,
|
||||
num_elements: i32,
|
||||
dc_key: u64,
|
||||
|
||||
@@ -72,10 +75,21 @@ impl Text {
|
||||
Vertex { pos: [x2, y2], color: [1., 1., 0., 1.], uv: [1., 1.] },
|
||||
];
|
||||
let indices = vec![0, 2, 1, 1, 2, 3];
|
||||
assert_eq!(atlas.uv_rects.len(), glyphs.len());
|
||||
|
||||
let num_elements = indices.len() as i32;
|
||||
let vertex_buffer = render_api.new_vertex_buffer(verts).await.unwrap();
|
||||
let index_buffer = render_api.new_index_buffer(indices).await.unwrap();
|
||||
let baseline_y = baseline.get_f32(0).unwrap();
|
||||
|
||||
let mut mesh = MeshBuilder::new();
|
||||
let mut glyph_pos_iter = GlyphPositionIter::new(font_size_val, &glyphs, baseline_y);
|
||||
for (uv_rect, glyph_rect) in atlas.uv_rects.into_iter().zip(glyph_pos_iter) {
|
||||
mesh.draw_box(&glyph_rect, COLOR_WHITE, &uv_rect);
|
||||
//mesh.draw_outline(&rect, COLOR_BLUE, 2.);
|
||||
}
|
||||
|
||||
let num_elements = mesh.num_elements();
|
||||
let (vertex_buffer, index_buffer) = mesh.alloc(&render_api).await.unwrap();
|
||||
|
||||
let sprites = glyphs.into_iter().map(|glyph| glyph.sprite).collect();
|
||||
|
||||
let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
|
||||
let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
|
||||
@@ -92,7 +106,9 @@ impl Text {
|
||||
render_api,
|
||||
text_shaper,
|
||||
tasks: on_modify.tasks,
|
||||
glyphs: Mutex::new((glyphs, atlas)),
|
||||
//glyphs: Mutex::new((glyphs, atlas)),
|
||||
glyph_sprites: Mutex::new(sprites),
|
||||
texture_id: atlas.texture_id,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_elements,
|
||||
@@ -130,7 +146,7 @@ impl Text {
|
||||
let mesh = DrawMesh {
|
||||
vertex_buffer: self.vertex_buffer,
|
||||
index_buffer: self.index_buffer,
|
||||
texture: Some(self.glyphs.lock().await.1.texture_id),
|
||||
texture: Some(self.texture_id),
|
||||
num_elements: self.num_elements,
|
||||
};
|
||||
|
||||
@@ -147,8 +163,8 @@ impl Text {
|
||||
|
||||
let off_x = rect.x / parent_rect.w;
|
||||
let off_y = rect.y / parent_rect.h;
|
||||
let scale_x = rect.w / parent_rect.w;
|
||||
let scale_y = rect.h / parent_rect.h;
|
||||
let scale_x = 1. / parent_rect.w;
|
||||
let scale_y = 1. / parent_rect.h;
|
||||
let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
|
||||
glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user