Просмотр исходного кода

wallet: use pages abstraction to cache drawcalls in a smarter way

darkfi 2 лет назад
Родитель
Сommit
df406f0787
2 измененных файлов с 271 добавлено и 139 удалено
  1. 23 2
      bin/darkwallet/src/mesh.rs
  2. 248 137
      bin/darkwallet/src/ui/chatview.rs

+ 23 - 2
bin/darkwallet/src/mesh.rs

@@ -18,9 +18,9 @@
 
 use crate::{
     error::Result,
-    gfx2::{Point, Rectangle, RenderApi, Vertex},
+    gfx2::{DrawMesh, Point, Rectangle, RenderApi, Vertex},
 };
-use miniquad::BufferId;
+use miniquad::{BufferId, TextureId};
 
 pub type Color = [f32; 4];
 
@@ -40,6 +40,27 @@ pub struct MeshInfo {
     pub num_elements: i32,
 }
 
+impl MeshInfo {
+    /// Convenience method
+    pub fn draw_with_texture(self, texture: TextureId) -> DrawMesh {
+        DrawMesh {
+            vertex_buffer: self.vertex_buffer,
+            index_buffer: self.index_buffer,
+            texture: Some(texture),
+            num_elements: self.num_elements,
+        }
+    }
+    /// Convenience method
+    pub fn draw_untextured(self) -> DrawMesh {
+        DrawMesh {
+            vertex_buffer: self.vertex_buffer,
+            index_buffer: self.index_buffer,
+            texture: None,
+            num_elements: self.num_elements,
+        }
+    }
+}
+
 pub struct MeshBuilder {
     verts: Vec<Vertex>,
     indices: Vec<u16>,

+ 248 - 137
bin/darkwallet/src/ui/chatview.rs

@@ -65,7 +65,7 @@ struct Message {
 }
 
 const LINES_PER_PAGE: usize = 10;
-const PRELOAD_PAGES: usize = 200;
+const PRELOAD_PAGES: usize = 10;
 
 #[derive(Clone)]
 struct Page {
@@ -73,6 +73,122 @@ struct Page {
     atlas: text2::RenderedAtlas,
 }
 
+#[derive(Clone)]
+struct PageMeshInfo {
+    px_height: f32,
+    mesh: DrawMesh,
+}
+
+type Page2Ptr = Arc<Page2>;
+
+struct Page2 {
+    msgs: Vec<Message>,
+    atlas: text2::RenderedAtlas,
+    // One draw call per page.
+    // Resizing the canvas means we recalc wrapping and the mesh changes
+    mesh_inf: SyncMutex<Option<PageMeshInfo>>,
+}
+
+impl Page2 {
+    async fn new(msgs: Vec<Message>, render_api: &RenderApi) -> Arc<Self> {
+        let mut atlas = text2::Atlas::new(render_api);
+        for msg in &msgs {
+            atlas.push(&msg.glyphs);
+        }
+        let Ok(atlas) = atlas.make().await else {
+            // what else should I do here?
+            panic!("unable to make atlas!");
+        };
+
+        Arc::new(Self { msgs, atlas, mesh_inf: SyncMutex::new(None) })
+    }
+
+    /// Regenerates the mesh, returning the old mesh which should be freed
+    async fn regen_mesh(
+        &self,
+        clip: &Rectangle,
+        render_api: &RenderApi,
+        font_size: f32,
+        line_height: f32,
+        baseline: f32,
+        nick_colors: &[Color],
+        timestamp_color: Color,
+        text_color: Color,
+    ) -> (PageMeshInfo, Option<DrawMesh>) {
+        // Nudge the bottom line up slightly, otherwise chars like p will cross the bottom.
+        //let descent = line_height - baseline;
+
+        let mut wrapped_line_idx = 0;
+
+        let mut mesh = MeshBuilder::new();
+
+        for msg in &self.msgs {
+            let glyphs = &msg.glyphs;
+
+            let nick_color = select_nick_color(&msg.chatmsg.nick, nick_colors);
+
+            // Keep track of the 'section'
+            // Section 0 is the timestamp
+            // Section 1 is the nickname (colorized)
+            // Finally is just the message itself
+            let mut section = 2;
+
+            let mut lines = text2::wrap(clip.w, font_size, glyphs);
+            // We are drawing bottom up but line wrap gives us lines in normal order
+            lines.reverse();
+            let last_idx = lines.len() - 1;
+            for (i, line) in lines.into_iter().enumerate() {
+                let off_y = wrapped_line_idx as f32 * line_height;
+
+                if i == last_idx {
+                    section = 0;
+                }
+
+                // Render line
+                let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &line, baseline);
+                for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) {
+                    let uv_rect =
+                        self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
+                    glyph_rect.y -= off_y;
+
+                    let color = match section {
+                        0 => timestamp_color,
+                        1 => nick_color,
+                        _ => text_color,
+                    };
+
+                    mesh.draw_box(&glyph_rect, color, uv_rect);
+
+                    if section < 2 && is_whitespace(&glyph.substr) {
+                        section += 1;
+                    }
+                }
+                wrapped_line_idx += 1;
+            }
+        }
+
+        let px_height = wrapped_line_idx as f32 * line_height;
+
+        let mesh = mesh.alloc(render_api).await.unwrap();
+        let mesh = mesh.draw_with_texture(self.atlas.texture_id);
+
+        let mesh_inf = PageMeshInfo { px_height, mesh };
+
+        let old = std::mem::replace(&mut *self.mesh_inf.lock().unwrap(), Some(mesh_inf.clone()));
+        let old = old.map(|v| v.mesh);
+
+        (mesh_inf, old)
+    }
+}
+
+fn select_nick_color(nick: &str, nick_colors: &[Color]) -> Color {
+    let mut hasher = DefaultHasher::new();
+    nick.hash(&mut hasher);
+    let i = hasher.finish() as usize;
+    let color = nick_colors[i % nick_colors.len()];
+    color
+}
+
 struct TouchInfo {
     start_scroll: f32,
     start_y: f32,
@@ -97,6 +213,7 @@ pub struct ChatView {
     tree: sled::Tree,
 
     pages: SyncMutex<Vec<Page>>,
+    pages2: SyncMutex<Vec<Page2Ptr>>,
     drawcalls: SyncMutex<Vec<DrawMesh>>,
     dc_key: u64,
 
@@ -189,6 +306,7 @@ impl ChatView {
                 tree,
 
                 pages: SyncMutex::new(Vec::new()),
+                pages2: SyncMutex::new(Vec::new()),
                 drawcalls: SyncMutex::new(Vec::new()),
                 dc_key: OsRng.gen(),
 
@@ -207,7 +325,9 @@ impl ChatView {
             }
         });
 
+        let timer = std::time::Instant::now();
         self_.populate().await;
+        debug!(target: "ui::chatview", "populate() took {:?}", timer.elapsed());
 
         Pimpl::ChatView(self_)
     }
@@ -261,10 +381,10 @@ impl ChatView {
 
         let mouse_pos = self.mouse_pos.lock().unwrap().clone();
         if !rect.contains(&mouse_pos) {
-            debug!(target: "ui::chatview", "not inside rect");
+            //debug!(target: "ui::chatview", "not inside rect");
             return
         }
-        debug!(target: "ui::chatview", "inside rect");
+        //debug!(target: "ui::chatview", "inside rect");
         let mut scroll = self.scroll.get() + wheel_y * 50.;
         if scroll < 0. {
             scroll = 0.;
@@ -353,6 +473,8 @@ impl ChatView {
 
     async fn populate(&self) {
         let mut pages = vec![];
+        let mut pages_len = 0;
+
         let mut msgs = vec![];
 
         for entry in self.tree.iter().rev() {
@@ -374,19 +496,12 @@ impl ChatView {
             msgs.push(Message { timest, chatmsg, glyphs });
 
             if msgs.len() >= LINES_PER_PAGE {
-                let mut atlas = text2::Atlas::new(&self.render_api);
-                for msg in &msgs {
-                    atlas.push(&msg.glyphs);
-                }
-                let Ok(atlas) = atlas.make().await else {
-                    // what else should I do here?
-                    panic!("unable to make atlas!");
-                };
+                let page = Page2::new(msgs.clone(), &self.render_api).await;
 
-                let page = Page { msgs: std::mem::take(&mut msgs), atlas };
-                pages.push(page);
+                self.pages2.lock().unwrap().push(page);
+                pages_len += 1;
 
-                if pages.len() >= PRELOAD_PAGES {
+                if pages_len >= PRELOAD_PAGES {
                     break
                 }
             }
@@ -395,6 +510,69 @@ impl ChatView {
         *self.pages.lock().unwrap() = pages;
     }
 
+    async fn draw_cached(&self, mut rect: Rectangle) -> Vec<DrawInstruction> {
+        let font_size = self.font_size.get();
+        let line_height = self.line_height.get();
+        let baseline = self.baseline.get();
+
+        let mut instrs = vec![];
+
+        let timest_color = self.timestamp_color.get();
+        let text_color = self.text_color.get();
+        let nick_colors = self.read_nick_colors();
+
+        // Nudge the bottom line up slightly, otherwise chars like p will cross the bottom.
+        let descent = line_height - baseline;
+
+        let pages = self.pages2.lock().unwrap().clone();
+        let mut current_height = descent;
+        for page in pages {
+            if current_height > rect.h {
+                break
+            }
+
+            let mesh_inf = page.mesh_inf.lock().unwrap().clone();
+            let mesh_inf = match mesh_inf {
+                Some(mesh_inf) => mesh_inf,
+                None => {
+                    let (mesh_inf, old_drawmesh) = page
+                        .regen_mesh(
+                            &rect,
+                            &self.render_api,
+                            font_size,
+                            line_height,
+                            baseline,
+                            &nick_colors,
+                            timest_color.clone(),
+                            text_color.clone(),
+                        )
+                        .await;
+                    assert!(old_drawmesh.is_none());
+                    mesh_inf
+                }
+            };
+
+            // Apply scroll and scissor
+            // We use the scissor for scrolling
+            // Because we use the scissor, our actual rect is now rect instead of parent_rect
+            let off_x = 0.;
+            // This calc decides whether scroll is in terms of pages or pixels
+            let off_y = (self.scroll.get() - current_height + rect.h) / rect.h;
+            let scale_x = 1. / rect.w;
+            let scale_y = 1. / 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.));
+
+            instrs.push(DrawInstruction::ApplyMatrix(model));
+
+            instrs.push(DrawInstruction::Draw(mesh_inf.mesh));
+
+            current_height += mesh_inf.px_height;
+        }
+
+        instrs
+    }
+
     /// Basically a version of redraw() where regen_mesh() is never called.
     /// Instead we use the cached version.
     async fn scrollview(&self) {
@@ -414,34 +592,14 @@ impl ChatView {
             panic!("Node {:?} bad rect property", node);
         };
 
-        //let mut drawcalls = self.regen_mesh(rect.clone()).await;
-
-        debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
-
-        // Apply scroll and scissor
-        // We use the scissor for scrolling
-        // Because we use the scissor, our actual rect is now rect instead of parent_rect
-        let off_x = 0.;
-        // This calc decides whether scroll is in terms of pages or pixels
-        let off_y = (self.scroll.get() + rect.h) / rect.h;
-        let scale_x = 1. / rect.w;
-        let scale_y = 1. / 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.));
-
-        let mut instrs =
-            vec![DrawInstruction::ApplyViewport(rect), DrawInstruction::ApplyMatrix(model)];
-        let drawcalls = self.drawcalls.lock().unwrap().clone();
-        let mut drawcalls: Vec<_> =
-            drawcalls.into_iter().map(|dc| DrawInstruction::Draw(dc)).collect();
-        instrs.append(&mut drawcalls);
+        let mut mesh_instrs = self.draw_cached(rect.clone()).await;
+        let mut instrs = vec![DrawInstruction::ApplyViewport(rect)];
+        instrs.append(&mut mesh_instrs);
 
         let draw_calls =
             vec![(self.dc_key, DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() })];
 
         self.render_api.replace_draw_calls(draw_calls).await;
-
-        debug!(target: "ui::chatview", "scrollview done");
     }
 
     async fn redraw(&self) {
@@ -467,91 +625,62 @@ impl ChatView {
         }
     }
 
-    async fn regen_mesh(&self, mut clip: Rectangle) -> Vec<DrawMesh> {
+    async fn regen_mesh(&self, mut rect: Rectangle) -> (Vec<DrawInstruction>, Vec<DrawMesh>) {
         let font_size = self.font_size.get();
         let line_height = self.line_height.get();
         let baseline = self.baseline.get();
-        // Draw time and nick, then go over each word. If word crosses end of line
-        // then apply a line break before the word and continue.
-        let pages = self.pages.lock().unwrap().clone();
 
-        let mut draws = vec![];
-        let color = COLOR_WHITE;
+        let mut instrs = vec![];
+        let mut old_drawmesh = vec![];
 
-        let timestamp_color = self.timestamp_color.get();
+        let timest_color = self.timestamp_color.get();
         let text_color = self.text_color.get();
         let nick_colors = self.read_nick_colors();
 
-        // This is a little hack to nudge the bottom line up slightly, otherwise
-        // chars like p will cross the bottom.
-        let descent = baseline / 2.;
+        // Nudge the bottom line up slightly, otherwise chars like p will cross the bottom.
+        let descent = line_height - baseline;
 
-        // Pages start at the bottom.
-        let mut current_idx = 0;
+        let pages = self.pages2.lock().unwrap().clone();
+        let mut current_height = descent;
         for page in pages {
-            let page_off_y = descent + baseline + current_idx as f32 * line_height;
-            if page_off_y > clip.h {
+            if current_height > rect.h {
                 break
             }
 
-            let mut mesh = MeshBuilder::new();
-
-            for msg in page.msgs {
-                let glyphs = msg.glyphs;
-
-                let nick_color = self.select_nick_color(&msg.chatmsg.nick, &nick_colors);
-
-                // Keep track of the 'section'
-                // Section 0 is the timestamp
-                // Section 1 is the nickname (colorized)
-                // Finally is just the message itself
-                let mut section = 2;
-
-                let mut lines = text2::wrap(clip.w, font_size, &glyphs);
-                // We are drawing bottom up but line wrap gives us lines in normal order
-                lines.reverse();
-                let last_idx = lines.len() - 1;
-                for (i, line) in lines.into_iter().enumerate() {
-                    let off_y = descent + baseline + current_idx as f32 * line_height;
-
-                    if i == last_idx {
-                        section = 0;
-                    }
-
-                    // Render line
-                    let mut glyph_pos_iter = GlyphPositionIter::new(font_size, &line, baseline);
-                    for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) {
-                        let uv_rect =
-                            page.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
-                        glyph_rect.y -= off_y;
-
-                        let color = match section {
-                            0 => timestamp_color,
-                            1 => nick_color,
-                            _ => text_color,
-                        };
-
-                        mesh.draw_box(&glyph_rect, color, uv_rect);
-
-                        if section < 2 && is_whitespace(&glyph.substr) {
-                            section += 1;
-                        }
-                    }
-
-                    current_idx += 1;
-                }
-            }
-
-            let mesh = mesh.alloc(&self.render_api).await.unwrap();
-
-            draws.push(DrawMesh {
-                vertex_buffer: mesh.vertex_buffer,
-                index_buffer: mesh.index_buffer,
-                texture: Some(page.atlas.texture_id),
-                num_elements: mesh.num_elements,
-            });
+            let (mesh_inf, old_drawmesh) = page
+                .regen_mesh(
+                    &rect,
+                    &self.render_api,
+                    font_size,
+                    line_height,
+                    baseline,
+                    &nick_colors,
+                    timest_color.clone(),
+                    text_color.clone(),
+                )
+                .await;
+
+            // Apply scroll and scissor
+            // We use the scissor for scrolling
+            // Because we use the scissor, our actual rect is now rect instead of parent_rect
+            let off_x = 0.;
+            // This calc decides whether scroll is in terms of pages or pixels
+            let off_y = (self.scroll.get() - current_height + rect.h) / rect.h;
+            let scale_x = 1. / rect.w;
+            let scale_y = 1. / 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.));
+
+            instrs.push(DrawInstruction::ApplyMatrix(model));
+
+            instrs.push(DrawInstruction::Draw(mesh_inf.mesh));
+
+            current_height += mesh_inf.px_height;
         }
 
+        (instrs, old_drawmesh)
+
+            /*
         if DEBUG_RENDER {
             let mut debug_mesh = MeshBuilder::new();
             debug_mesh.draw_outline(
@@ -569,6 +698,7 @@ impl ChatView {
         }
 
         draws
+        */
     }
 
     fn read_nick_colors(&self) -> Vec<Color> {
@@ -606,43 +736,24 @@ impl ChatView {
             panic!("Node {:?} bad rect property", node);
         };
 
-        // TODO: Do we need this? Because of the viewport clipping
-        rect.x += parent_rect.x;
-        rect.y += parent_rect.y;
-
-        let drawcalls = self.regen_mesh(rect.clone()).await;
-        let old_drawcalls =
-            std::mem::replace(&mut *self.drawcalls.lock().unwrap(), drawcalls.clone());
-        let mut drawcalls: Vec<_> =
-            drawcalls.into_iter().map(|dc| DrawInstruction::Draw(dc)).collect();
+        let timer = std::time::Instant::now();
+        let (mut mesh_instrs, mut old_drawmesh) = self.regen_mesh(rect.clone()).await;
+        debug!(target: "ui::chatview", "regen_mesh() took {:?}", timer.elapsed());
 
         let mut freed_textures = vec![];
         let mut freed_buffers = vec![];
-        for old_dc in old_drawcalls {
-            freed_buffers.push(old_dc.vertex_buffer);
-            freed_buffers.push(old_dc.index_buffer);
-            if let Some(texture_id) = old_dc.texture {
-                freed_textures.push(texture_id);
-            }
+        for old_mesh in old_drawmesh {
+            freed_buffers.push(old_mesh.vertex_buffer);
+            freed_buffers.push(old_mesh.index_buffer);
+            //if let Some(texture_id) = old_dc.texture {
+            //    freed_textures.push(texture_id);
+            //}
         }
 
         debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
 
-        // Apply scroll and scissor
-        // We use the scissor for scrolling
-        // Because we use the scissor, our actual rect is now rect instead of parent_rect
-        let off_x = 0.;
-        // This calc decides whether scroll is in terms of pages or pixels
-        let off_y = (self.scroll.get() + rect.h) / rect.h;
-        let scale_x = 1. / rect.w;
-        let scale_y = 1. / 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.));
-
-        let mut instrs =
-            vec![DrawInstruction::ApplyViewport(rect), DrawInstruction::ApplyMatrix(model)];
-        //let mut instrs = vec![DrawInstruction::ApplyMatrix(model)];
-        instrs.append(&mut drawcalls);
+        let mut instrs = vec![DrawInstruction::ApplyViewport(rect)];
+        instrs.append(&mut mesh_instrs);
 
         Some(DrawUpdate {
             key: self.dc_key,