more efficient rendering

macos-click-through
keith 4 years ago
parent ee2ee53614
commit a85c7dda3e

@ -1,4 +1,3 @@
use log::trace;
use std::sync::Arc; use std::sync::Arc;
use super::style::Style; use super::style::Style;
@ -8,9 +7,7 @@ pub type GridCell = Option<(String, Option<Arc<Style>>)>;
pub struct CharacterGrid { pub struct CharacterGrid {
pub width: u64, pub width: u64,
pub height: u64, pub height: u64,
pub should_clear: bool,
dirty: Vec<bool>,
characters: Vec<GridCell>, characters: Vec<GridCell>,
} }
@ -20,17 +17,13 @@ impl CharacterGrid {
let cell_count = (width * height) as usize; let cell_count = (width * height) as usize;
CharacterGrid { CharacterGrid {
characters: vec![None; cell_count], characters: vec![None; cell_count],
dirty: vec![true; cell_count],
width, width,
height, height,
should_clear: false,
} }
} }
pub fn resize(&mut self, width: u64, height: u64) { pub fn resize(&mut self, width: u64, height: u64) {
trace!("Editor resized");
let new_cell_count = (width * height) as usize; let new_cell_count = (width * height) as usize;
let new_dirty = vec![false; new_cell_count];
let default_cell: GridCell = None; let default_cell: GridCell = None;
let mut new_characters = vec![default_cell; new_cell_count]; let mut new_characters = vec![default_cell; new_cell_count];
@ -44,15 +37,11 @@ impl CharacterGrid {
self.width = width; self.width = width;
self.height = height; self.height = height;
self.dirty = new_dirty;
self.characters = new_characters; self.characters = new_characters;
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
trace!("Editor cleared");
self.set_characters_all(None); self.set_characters_all(None);
self.set_dirty_all(true);
self.should_clear = true;
} }
fn cell_index(&self, x: u64, y: u64) -> Option<usize> { fn cell_index(&self, x: u64, y: u64) -> Option<usize> {
@ -72,26 +61,6 @@ impl CharacterGrid {
.map(move |idx| &mut self.characters[idx]) .map(move |idx| &mut self.characters[idx])
} }
pub fn is_dirty_cell(&self, x: u64, y: u64) -> bool {
if let Some(idx) = self.cell_index(x, y) {
self.dirty[idx]
} else {
false
}
}
pub fn set_dirty_cell(&mut self, x: u64, y: u64) {
if let Some(idx) = self.cell_index(x, y) {
self.dirty[idx] = true;
}
}
pub fn set_dirty_all(&mut self, value: bool) {
self.dirty.clear();
self.dirty
.resize_with((self.width * self.height) as usize, || value);
}
pub fn set_characters_all(&mut self, value: GridCell) { pub fn set_characters_all(&mut self, value: GridCell) {
self.characters.clear(); self.characters.clear();
self.characters self.characters
@ -100,10 +69,12 @@ impl CharacterGrid {
}); });
} }
pub fn rows(&self) -> impl Iterator<Item = &[GridCell]> { pub fn row(&self, row_index: u64) -> Option<&[GridCell]> {
(0..self.height).map(move |row| { if row_index < self.height {
&self.characters[(row * self.width) as usize..((row + 1) * self.width) as usize] Some(&self.characters[(row_index * self.width) as usize..((row_index + 1) * self.width) as usize])
}) } else {
None
}
} }
} }
@ -156,9 +127,7 @@ mod tests {
let character_grid = CharacterGrid::new(context.size); let character_grid = CharacterGrid::new(context.size);
assert_eq!(character_grid.width, context.size.0); assert_eq!(character_grid.width, context.size.0);
assert_eq!(character_grid.height, context.size.1); assert_eq!(character_grid.height, context.size.1);
assert_eq!(character_grid.should_clear, true);
assert_eq!(character_grid.characters, vec![None; context.area]); assert_eq!(character_grid.characters, vec![None; context.area]);
assert_eq!(character_grid.dirty, vec![true; context.area]);
} }
#[test] #[test]
@ -217,37 +186,6 @@ mod tests {
); );
} }
#[test]
fn test_is_dirty_cell() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
character_grid.dirty[context.index] = false;
// RUN FUNCTION
assert!(!character_grid.is_dirty_cell(context.x, context.y));
}
#[test]
fn test_set_dirty_cell() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
character_grid.dirty = vec![false; context.area];
// RUN FUNCTION
character_grid.set_dirty_cell(context.x, context.y);
assert!(character_grid.dirty[context.index]);
}
#[test]
fn test_set_dirty_all() {
let context = Context::new();
let mut character_grid = CharacterGrid::new(context.size);
// RUN FUNCTION
character_grid.set_dirty_all(false);
assert_eq!(character_grid.dirty, vec![false; context.area]);
}
#[test] #[test]
fn test_set_characters_all() { fn test_set_characters_all() {
let context = Context::new(); let context = Context::new();
@ -274,18 +212,14 @@ mod tests {
"foo".to_string(), "foo".to_string(),
Some(Arc::new(Style::new(context.none_colors))), Some(Arc::new(Style::new(context.none_colors))),
)); ));
character_grid.dirty = vec![false; context.area];
character_grid.characters = vec![grid_cell.clone(); context.area]; character_grid.characters = vec![grid_cell.clone(); context.area];
character_grid.should_clear = false;
// RUN FUNCTION // RUN FUNCTION
character_grid.clear(); character_grid.clear();
assert_eq!(character_grid.width, context.size.0); assert_eq!(character_grid.width, context.size.0);
assert_eq!(character_grid.height, context.size.1); assert_eq!(character_grid.height, context.size.1);
assert_eq!(character_grid.should_clear, true);
assert_eq!(character_grid.characters, vec![None; context.area]); assert_eq!(character_grid.characters, vec![None; context.area]);
assert_eq!(character_grid.dirty, vec![true; context.area]);
} }
#[test] #[test]
@ -302,18 +236,14 @@ mod tests {
"foo".to_string(), "foo".to_string(),
Some(Arc::new(Style::new(context.none_colors))), Some(Arc::new(Style::new(context.none_colors))),
)); ));
character_grid.dirty = vec![false; context.area];
character_grid.characters = vec![grid_cell.clone(); context.area]; character_grid.characters = vec![grid_cell.clone(); context.area];
character_grid.should_clear = false;
// RUN FUNCTION // RUN FUNCTION
character_grid.resize(width, height); character_grid.resize(width, height);
assert_eq!(character_grid.width, width); assert_eq!(character_grid.width, width);
assert_eq!(character_grid.height, height); assert_eq!(character_grid.height, height);
assert_eq!(character_grid.should_clear, true);
assert_eq!(character_grid.characters, vec![None; new_area]); assert_eq!(character_grid.characters, vec![None; new_area]);
assert_eq!(character_grid.dirty, vec![true; new_area]);
} }
#[test] #[test]

@ -31,7 +31,6 @@ pub struct WindowRenderInfo {
pub grid_position: (f64, f64), pub grid_position: (f64, f64),
pub width: u64, pub width: u64,
pub height: u64, pub height: u64,
pub should_clear: bool,
pub draw_commands: Vec<DrawCommand> pub draw_commands: Vec<DrawCommand>
} }
@ -143,7 +142,7 @@ impl Editor {
RedrawEvent::Clear { grid } => { RedrawEvent::Clear { grid } => {
self.windows self.windows
.get_mut(&grid) .get_mut(&grid)
.map(|window| window.grid.clear()); .map(|window| window.clear());
} }
RedrawEvent::Destroy { grid } => self.close_window(grid), RedrawEvent::Destroy { grid } => self.close_window(grid),
RedrawEvent::Scroll { RedrawEvent::Scroll {
@ -190,12 +189,6 @@ impl Editor {
} }
fn close_window(&mut self, grid: u64) { fn close_window(&mut self, grid: u64) {
self.windows
.get(&grid)
.and_then(|window| window.anchor_grid_id)
.and_then(|parent_window_id| self.windows.get_mut(&parent_window_id))
.map(|parent_window| parent_window.children.remove(&grid));
self.windows.remove(&grid); self.windows.remove(&grid);
self.closed_window_ids.insert(grid); self.closed_window_ids.insert(grid);
} }
@ -246,10 +239,6 @@ impl Editor {
} else { } else {
error!("Attempted to float window that does not exist."); error!("Attempted to float window that does not exist.");
} }
if let Some(anchor_window) = self.windows.get_mut(&anchor_grid) {
anchor_window.children.insert(grid);
}
} }
fn set_message_position(&mut self, grid: u64, row: u64) { fn set_message_position(&mut self, grid: u64, row: u64) {
@ -271,10 +260,6 @@ impl Editor {
); );
self.windows.insert(grid, new_window); self.windows.insert(grid, new_window);
} }
if let Some(parent) = self.windows.get_mut(&1) {
parent.children.insert(grid);
}
} }
fn get_window_top_left(&self, grid: u64) -> Option<(f64, f64)> { fn get_window_top_left(&self, grid: u64) -> Option<(f64, f64)> {
@ -339,20 +324,19 @@ impl Editor {
} }
} }
fn build_window_render_info(&mut self, grid: u64) -> Option<WindowRenderInfo> { fn build_window_render_info(&mut self, grid_id: u64) -> Option<WindowRenderInfo> {
let grid_position = self.get_window_top_left(grid)?; let grid_position = self.get_window_top_left(grid_id)?;
let window = self.windows.get_mut(&grid)?; let window = self.windows.get_mut(&grid_id)?;
let (draw_commands, should_clear) = window.build_draw_commands(); let draw_commands = window.build_draw_commands();
let width = window.grid.width; let width = window.grid.width;
let height = window.grid.height; let height = window.grid.height;
Some(WindowRenderInfo { Some(WindowRenderInfo {
grid_id: grid, grid_id,
grid_position, grid_position,
width, width,
height, height,
should_clear,
draw_commands draw_commands
}) })
} }

@ -1,7 +1,8 @@
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::mem::swap;
use log::trace; use log::{trace, warn};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::grid::CharacterGrid; use super::grid::CharacterGrid;
@ -9,11 +10,23 @@ use super::style::Style;
use crate::bridge::{GridLineCell, WindowAnchor}; use crate::bridge::{GridLineCell, WindowAnchor};
#[derive(new, Debug, Clone)] #[derive(new, Debug, Clone)]
pub struct DrawCommand { pub enum DrawCommand {
pub text: String, Cell {
pub cell_width: u64, text: String,
pub grid_position: (u64, u64), cell_width: u64,
pub style: Option<Arc<Style>>, grid_position: (u64, u64),
style: Option<Arc<Style>>,
},
Scroll {
top: u64,
bot: u64,
left: u64,
right: u64,
rows: i64,
cols: i64,
},
Resize,
Clear
} }
pub struct Window { pub struct Window {
@ -24,7 +37,7 @@ pub struct Window {
pub anchor_type: WindowAnchor, pub anchor_type: WindowAnchor,
pub anchor_row: f64, pub anchor_row: f64,
pub anchor_column: f64, pub anchor_column: f64,
pub children: HashSet<u64>, pub queued_draw_commands: Vec<DrawCommand>
} }
impl Window { impl Window {
@ -45,12 +58,18 @@ impl Window {
anchor_column, anchor_column,
grid: CharacterGrid::new((width, height)), grid: CharacterGrid::new((width, height)),
hidden: false, hidden: false,
children: HashSet::new(), queued_draw_commands: Vec::new()
} }
} }
pub fn resize(&mut self, width: u64, height: u64) { pub fn resize(&mut self, width: u64, height: u64) {
self.grid.resize(width, height); self.grid.resize(width, height);
self.queued_draw_commands.push(DrawCommand::Resize)
}
pub fn clear(&mut self) {
self.grid.clear();
self.queued_draw_commands.push(DrawCommand::Clear);
} }
fn draw_grid_line_cell( fn draw_grid_line_cell(
@ -73,23 +92,61 @@ impl Window {
text = text.repeat(times as usize); text = text.repeat(times as usize);
} }
let mut draw_command_start_index = column_pos.clone();
if text.is_empty() { if text.is_empty() {
if let Some(cell) = self.grid.get_cell_mut(*column_pos, row_index) { if let Some(cell) = self.grid.get_cell_mut(*column_pos, row_index) {
*cell = Some(("".to_string(), style.clone())); *cell = Some(("".to_string(), style.clone()));
} }
self.grid.set_dirty_cell(*column_pos, row_index);
*column_pos += 1; *column_pos += 1;
} else { } else {
for (i, character) in text.graphemes(true).enumerate() { for (i, character) in text.graphemes(true).enumerate() {
if let Some(cell) = self.grid.get_cell_mut(i as u64 + *column_pos, row_index) { if let Some(cell) = self.grid.get_cell_mut(i as u64 + *column_pos, row_index) {
*cell = Some((character.to_string(), style.clone())); *cell = Some((character.to_string(), style.clone()));
self.grid.set_dirty_cell(*column_pos, row_index);
} }
} }
*column_pos += text.graphemes(true).count() as u64; *column_pos += text.graphemes(true).count() as u64;
} }
let row = self.grid.row(row_index).unwrap();
loop {
if draw_command_start_index > 0 {
if let Some((_, previous_style)) = &row[draw_command_start_index as usize - 1] {
if &style == previous_style {
draw_command_start_index = draw_command_start_index - 1;
continue;
}
}
}
break;
}
let mut draw_command_end_index = column_pos.clone() - 1;
loop {
if draw_command_end_index < self.grid.width - 1 {
if let Some((_, next_style)) = &row[draw_command_end_index as usize] {
if &style == next_style {
draw_command_end_index = draw_command_end_index + 1;
continue;
}
}
}
break;
}
let mut text = String::new();
for x in draw_command_start_index..draw_command_end_index {
let (character, _) = row[x as usize].as_ref().unwrap();
text.push_str(character);
}
self.queued_draw_commands.push(DrawCommand::Cell {
text,
cell_width: draw_command_end_index - draw_command_start_index,
grid_position: (draw_command_start_index, row_index),
style: style.clone()
});
*previous_style = style; *previous_style = style;
} }
@ -113,7 +170,7 @@ impl Window {
); );
} }
} else { } else {
println!("Draw command out of bounds"); warn!("Draw command out of bounds");
} }
} }
@ -150,114 +207,23 @@ impl Window {
self.grid.get_cell_mut(dest_x as u64, dest_y as u64) self.grid.get_cell_mut(dest_x as u64, dest_y as u64)
{ {
*dest_cell = cell_data; *dest_cell = cell_data;
self.grid.set_dirty_cell(dest_x as u64, dest_y as u64);
}
}
}
}
}
trace!("Region scrolled");
}
pub fn build_draw_commands(&mut self) -> (Vec<DrawCommand>, bool) {
let mut draw_commands = Vec::new();
for (row_index, row) in self.grid.rows().enumerate() {
let mut command = None;
fn add_command(commands_list: &mut Vec<DrawCommand>, command: Option<DrawCommand>) {
if let Some(command) = command {
commands_list.push(command);
}
}
fn command_matches(command: &Option<DrawCommand>, style: &Option<Arc<Style>>) -> bool {
match command {
Some(command) => &command.style == style,
None => true,
}
}
fn add_character(
command: &mut Option<DrawCommand>,
character: &str,
row_index: u64,
col_index: u64,
style: Option<Arc<Style>>,
) {
match command {
Some(command) => {
command.text.push_str(character);
command.cell_width += 1;
}
None => {
command.replace(DrawCommand::new(
character.to_string(),
1,
(col_index, row_index),
style,
));
}
}
}
for (col_index, cell) in row.iter().enumerate() {
if let Some((character, style)) = cell {
if character.is_empty() {
add_character(
&mut command,
&" ",
row_index as u64,
col_index as u64,
style.clone(),
);
add_command(&mut draw_commands, command);
command = None;
} else {
if !command_matches(&command, &style) {
add_command(&mut draw_commands, command);
command = None;
} }
add_character(
&mut command,
&character,
row_index as u64,
col_index as u64,
style.clone(),
);
} }
} else {
if !command_matches(&command, &None) {
add_command(&mut draw_commands, command);
command = None;
}
add_character(&mut command, " ", row_index as u64, col_index as u64, None);
} }
} }
add_command(&mut draw_commands, command);
} }
let should_clear = self.grid.should_clear; self.queued_draw_commands.push(DrawCommand::Scroll {
let draw_commands = draw_commands top, bot, left, right, rows, cols
.into_iter() });
.filter(|command| { }
let (x, y) = command.grid_position;
let min = (x as i64 - 1).max(0) as u64;
let max = (x + command.cell_width + 1).min(self.grid.width);
for char_index in min..max { pub fn build_draw_commands(&mut self) -> Vec<DrawCommand> {
if self.grid.is_dirty_cell(char_index, y) {
return true;
}
}
false
})
.collect::<Vec<DrawCommand>>();
self.grid.set_dirty_all(false); let mut draw_commands = Vec::new();
self.grid.should_clear = false; swap(&mut self.queued_draw_commands, &mut draw_commands);
trace!("Draw commands sent"); trace!("Draw commands sent");
(draw_commands, should_clear) draw_commands
} }
} }

@ -4,8 +4,9 @@ use std::sync::Arc;
use log::trace; use log::trace;
use skulpin::skia_safe::gpu::SurfaceOrigin; use skulpin::skia_safe::gpu::SurfaceOrigin;
use skulpin::skia_safe::{ use skulpin::skia_safe::{
colors, dash_path_effect, Budgeted, Canvas, ImageInfo, Paint, Rect, Surface, Point, Color, image_filters::* colors, dash_path_effect, Budgeted, Canvas, ImageInfo, Paint, Rect, Surface, Point
}; };
use skulpin::skia_safe::canvas::SrcRectConstraint;
use skulpin::CoordinateSystemHelper; use skulpin::CoordinateSystemHelper;
mod caching_shaper; mod caching_shaper;
@ -17,7 +18,7 @@ pub use caching_shaper::CachingShaper;
pub use font_options::*; pub use font_options::*;
use animation_utils::*; use animation_utils::*;
use crate::editor::{Style, WindowRenderInfo, EDITOR}; use crate::editor::{Style, WindowRenderInfo, EDITOR, DrawCommand};
use crate::redraw_scheduler::REDRAW_SCHEDULER; use crate::redraw_scheduler::REDRAW_SCHEDULER;
use crate::settings::*; use crate::settings::*;
use cursor_renderer::CursorRenderer; use cursor_renderer::CursorRenderer;
@ -266,7 +267,7 @@ impl Renderer {
&mut self, &mut self,
settings: &RendererSettings, settings: &RendererSettings,
root_canvas: &mut Canvas, root_canvas: &mut Canvas,
window_render_info: &WindowRenderInfo, window_render_info: WindowRenderInfo,
default_style: &Arc<Style>, default_style: &Arc<Style>,
dt: f32 dt: f32
) -> (u64, Rect) { ) -> (u64, Rect) {
@ -276,45 +277,70 @@ impl Renderer {
let image_width = (window_render_info.width as f32 * self.font_width) as i32; let image_width = (window_render_info.width as f32 * self.font_width) as i32;
let image_height = (window_render_info.height as f32 * self.font_height) as i32; let image_height = (window_render_info.height as f32 * self.font_height) as i32;
let mut rendered_window = if window_render_info.should_clear { let mut rendered_window = self.rendered_windows
None .remove(&window_render_info.grid_id)
} else { .unwrap_or_else(|| {
self.rendered_windows.remove(&window_render_info.grid_id) let surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
} RenderedWindow::new(surface, window_destination)
.unwrap_or_else(|| { });
let surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
RenderedWindow::new(surface, window_destination) for command in window_render_info.draw_commands.into_iter() {
}); match command {
DrawCommand::Cell {
if rendered_window.surface.width() != image_width || rendered_window.surface.height() != image_height { text, cell_width, grid_position, style
let mut old_surface = rendered_window.surface; } => {
rendered_window.surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height)); let mut canvas = rendered_window.surface.canvas();
old_surface.draw(rendered_window.surface.canvas(), (0.0, 0.0), None); self.draw_background(
} &mut canvas,
grid_position,
rendered_window.update(settings, window_destination, dt); cell_width,
&style,
let mut canvas = rendered_window.surface.canvas(); &default_style,
);
for command in window_render_info.draw_commands.iter() { self.draw_foreground(
self.draw_background( &mut canvas,
&mut canvas, &text,
command.grid_position, grid_position,
command.cell_width, cell_width,
&command.style, &style,
&default_style, &default_style,
); );
},
DrawCommand::Scroll {
top, bot, left, right, rows, cols
} => {
let scrolled_region = Rect::new(
left as f32 * self.font_width,
top as f32 * self.font_height,
right as f32 * self.font_width,
bot as f32 * self.font_height);
let snapshot = rendered_window.surface.image_snapshot();
let canvas = rendered_window.surface.canvas();
canvas.save();
canvas.clip_rect(scrolled_region, None, Some(false));
let mut translated_region = scrolled_region.clone();
translated_region.offset((-cols as f32 * self.font_width, -rows as f32 * self.font_height));
canvas.draw_image_rect(snapshot, Some((&scrolled_region, SrcRectConstraint::Fast)), translated_region, &self.paint);
canvas.restore();
},
DrawCommand::Resize => {
let mut old_surface = rendered_window.surface;
rendered_window.surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
old_surface.draw(rendered_window.surface.canvas(), (0.0, 0.0), None);
},
DrawCommand::Clear => {
rendered_window.surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
}
}
} }
for command in window_render_info.draw_commands.iter() { if rendered_window.update(settings, window_destination, dt) {
self.draw_foreground( REDRAW_SCHEDULER.queue_next_frame();
&mut canvas,
&command.text,
command.grid_position,
command.cell_width,
&command.style,
&default_style,
);
} }
root_canvas.save_layer(&Default::default()); root_canvas.save_layer(&Default::default());
@ -340,7 +366,6 @@ impl Renderer {
) -> bool { ) -> bool {
trace!("Rendering"); trace!("Rendering");
REDRAW_SCHEDULER.queue_next_frame();
let settings = SETTINGS.get::<RendererSettings>(); let settings = SETTINGS.get::<RendererSettings>();
let (render_info, default_style, cursor, guifont_setting) = { let (render_info, default_style, cursor, guifont_setting) = {
@ -366,7 +391,7 @@ impl Renderer {
coordinate_system_helper.use_logical_coordinates(gpu_canvas); coordinate_system_helper.use_logical_coordinates(gpu_canvas);
self.window_regions = render_info.windows self.window_regions = render_info.windows
.iter() .into_iter()
.map(|window_render_info| self.draw_window(&settings, gpu_canvas, window_render_info, &default_style, dt)) .map(|window_render_info| self.draw_window(&settings, gpu_canvas, window_render_info, &default_style, dt))
.collect(); .collect();

Loading…
Cancel
Save