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

This commit is contained in:
darkfi
2024-07-30 12:35:18 +02:00
parent 687a1a54ad
commit df406f0787
2 changed files with 267 additions and 135 deletions

View File

@@ -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>,

View File

@@ -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();
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;
for msg in page.msgs {
let glyphs = msg.glyphs;
// 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.));
let nick_color = self.select_nick_color(&msg.chatmsg.nick, &nick_colors);
instrs.push(DrawInstruction::ApplyMatrix(model));
// 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;
instrs.push(DrawInstruction::Draw(mesh_inf.mesh));
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,
});
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,