Kaynağa Gözat

wallet: for labels, create a texture atlas so quads can be batched as a single draw call

darkfi 2 yıl önce
ebeveyn
işleme
cbff8febe1

+ 1 - 1
bin/darkwallet/src/app.rs

@@ -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();

+ 18 - 0
bin/darkwallet/src/gfx2.rs

@@ -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. }
     }

+ 1 - 0
bin/darkwallet/src/main.rs

@@ -22,6 +22,7 @@ mod expr;
 //mod gfx;
 mod gfx2;
 mod keysym;
+mod mesh;
 mod net;
 //mod plugin;
 mod prop;

+ 84 - 0
bin/darkwallet/src/mesh.rs

@@ -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))
+    }
+}

+ 127 - 26
bin/darkwallet/src/text2.rs

@@ -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,
+        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);
+
+                // 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
+            }
         };
-        uv_rects.push(uv_rect);
 
-        current_x += sprite.bmp_width + ATLAS_GAP;
+        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 {

+ 27 - 11
bin/darkwallet/src/ui/text.rs

@@ -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.));