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

@ -31,7 +31,6 @@ pub struct WindowRenderInfo {
pub grid_position: (f64, f64),
pub width: u64,
pub height: u64,
pub should_clear: bool,
pub draw_commands: Vec<DrawCommand>
}
@ -143,7 +142,7 @@ impl Editor {
RedrawEvent::Clear { grid } => {
self.windows
.get_mut(&grid)
.map(|window| window.grid.clear());
.map(|window| window.clear());
}
RedrawEvent::Destroy { grid } => self.close_window(grid),
RedrawEvent::Scroll {
@ -190,12 +189,6 @@ impl Editor {
}
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.closed_window_ids.insert(grid);
}
@ -246,10 +239,6 @@ impl Editor {
} else {
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) {
@ -271,10 +260,6 @@ impl Editor {
);
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)> {
@ -339,20 +324,19 @@ impl Editor {
}
}
fn build_window_render_info(&mut self, grid: u64) -> Option<WindowRenderInfo> {
let grid_position = self.get_window_top_left(grid)?;
let window = self.windows.get_mut(&grid)?;
fn build_window_render_info(&mut self, grid_id: u64) -> Option<WindowRenderInfo> {
let grid_position = self.get_window_top_left(grid_id)?;
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 height = window.grid.height;
Some(WindowRenderInfo {
grid_id: grid,
grid_id,
grid_position,
width,
height,
should_clear,
draw_commands
})
}

@ -1,7 +1,8 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::sync::Arc;
use std::mem::swap;
use log::trace;
use log::{trace, warn};
use unicode_segmentation::UnicodeSegmentation;
use super::grid::CharacterGrid;
@ -9,11 +10,23 @@ use super::style::Style;
use crate::bridge::{GridLineCell, WindowAnchor};
#[derive(new, Debug, Clone)]
pub struct DrawCommand {
pub text: String,
pub cell_width: u64,
pub grid_position: (u64, u64),
pub style: Option<Arc<Style>>,
pub enum DrawCommand {
Cell {
text: String,
cell_width: u64,
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 {
@ -24,7 +37,7 @@ pub struct Window {
pub anchor_type: WindowAnchor,
pub anchor_row: f64,
pub anchor_column: f64,
pub children: HashSet<u64>,
pub queued_draw_commands: Vec<DrawCommand>
}
impl Window {
@ -45,12 +58,18 @@ impl Window {
anchor_column,
grid: CharacterGrid::new((width, height)),
hidden: false,
children: HashSet::new(),
queued_draw_commands: Vec::new()
}
}
pub fn resize(&mut self, width: u64, height: u64) {
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(
@ -73,23 +92,61 @@ impl Window {
text = text.repeat(times as usize);
}
let mut draw_command_start_index = column_pos.clone();
if text.is_empty() {
if let Some(cell) = self.grid.get_cell_mut(*column_pos, row_index) {
*cell = Some(("".to_string(), style.clone()));
}
self.grid.set_dirty_cell(*column_pos, row_index);
*column_pos += 1;
} else {
for (i, character) in text.graphemes(true).enumerate() {
if let Some(cell) = self.grid.get_cell_mut(i as u64 + *column_pos, row_index) {
*cell = Some((character.to_string(), style.clone()));
self.grid.set_dirty_cell(*column_pos, row_index);
}
}
*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;
}
@ -113,7 +170,7 @@ impl Window {
);
}
} 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)
{
*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;
let draw_commands = draw_commands
.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);
self.queued_draw_commands.push(DrawCommand::Scroll {
top, bot, left, right, rows, cols
});
}
for char_index in min..max {
if self.grid.is_dirty_cell(char_index, y) {
return true;
}
}
false
})
.collect::<Vec<DrawCommand>>();
pub fn build_draw_commands(&mut self) -> Vec<DrawCommand> {
self.grid.set_dirty_all(false);
self.grid.should_clear = false;
let mut draw_commands = Vec::new();
swap(&mut self.queued_draw_commands, &mut draw_commands);
trace!("Draw commands sent");
(draw_commands, should_clear)
draw_commands
}
}

@ -4,8 +4,9 @@ use std::sync::Arc;
use log::trace;
use skulpin::skia_safe::gpu::SurfaceOrigin;
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;
mod caching_shaper;
@ -17,7 +18,7 @@ pub use caching_shaper::CachingShaper;
pub use font_options::*;
use animation_utils::*;
use crate::editor::{Style, WindowRenderInfo, EDITOR};
use crate::editor::{Style, WindowRenderInfo, EDITOR, DrawCommand};
use crate::redraw_scheduler::REDRAW_SCHEDULER;
use crate::settings::*;
use cursor_renderer::CursorRenderer;
@ -266,7 +267,7 @@ impl Renderer {
&mut self,
settings: &RendererSettings,
root_canvas: &mut Canvas,
window_render_info: &WindowRenderInfo,
window_render_info: WindowRenderInfo,
default_style: &Arc<Style>,
dt: f32
) -> (u64, Rect) {
@ -276,45 +277,70 @@ impl Renderer {
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 mut rendered_window = if window_render_info.should_clear {
None
} else {
self.rendered_windows.remove(&window_render_info.grid_id)
}
.unwrap_or_else(|| {
let surface = self.build_window_surface(root_canvas, &default_style, (image_width, image_height));
RenderedWindow::new(surface, window_destination)
});
if rendered_window.surface.width() != image_width || rendered_window.surface.height() != image_height {
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);
}
rendered_window.update(settings, window_destination, dt);
let mut canvas = rendered_window.surface.canvas();
for command in window_render_info.draw_commands.iter() {
self.draw_background(
&mut canvas,
command.grid_position,
command.cell_width,
&command.style,
&default_style,
);
let mut rendered_window = self.rendered_windows
.remove(&window_render_info.grid_id)
.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 {
text, cell_width, grid_position, style
} => {
let mut canvas = rendered_window.surface.canvas();
self.draw_background(
&mut canvas,
grid_position,
cell_width,
&style,
&default_style,
);
self.draw_foreground(
&mut canvas,
&text,
grid_position,
cell_width,
&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() {
self.draw_foreground(
&mut canvas,
&command.text,
command.grid_position,
command.cell_width,
&command.style,
&default_style,
);
if rendered_window.update(settings, window_destination, dt) {
REDRAW_SCHEDULER.queue_next_frame();
}
root_canvas.save_layer(&Default::default());
@ -340,7 +366,6 @@ impl Renderer {
) -> bool {
trace!("Rendering");
REDRAW_SCHEDULER.queue_next_frame();
let settings = SETTINGS.get::<RendererSettings>();
let (render_info, default_style, cursor, guifont_setting) = {
@ -366,7 +391,7 @@ impl Renderer {
coordinate_system_helper.use_logical_coordinates(gpu_canvas);
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))
.collect();

Loading…
Cancel
Save