mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
wallet: use pages abstraction to cache drawcalls in a smarter way
This commit is contained in:
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user