mirror of
https://github.com/penpot/penpot.git
synced 2025-06-24 19:07:00 +02:00
Merge pull request #6591 from penpot/azazeln28-refactor-render-iteration
♻️ Refactor render iteration
This commit is contained in:
commit
c1fa6be7c4
2 changed files with 247 additions and 184 deletions
|
@ -20,7 +20,7 @@ use surfaces::{SurfaceId, Surfaces};
|
||||||
|
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::{modified_children_ids, Corners, Shape, StrokeKind, StructureEntry, Type};
|
use crate::shapes::{modified_children_ids, Corners, Shape, StrokeKind, StructureEntry, Type};
|
||||||
use crate::tiles::{self, TileRect, TileViewbox, TileWithDistance};
|
use crate::tiles::{self, PendingTiles, TileRect};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
use crate::wapi;
|
use crate::wapi;
|
||||||
|
@ -31,41 +31,9 @@ pub use images::*;
|
||||||
|
|
||||||
// This is the extra are used for tile rendering.
|
// This is the extra are used for tile rendering.
|
||||||
const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1;
|
const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1;
|
||||||
const VIEWPORT_DEFAULT_CAPACITY: usize = 24 * 12;
|
|
||||||
const MAX_BLOCKING_TIME_MS: i32 = 32;
|
const MAX_BLOCKING_TIME_MS: i32 = 32;
|
||||||
const NODE_BATCH_THRESHOLD: i32 = 10;
|
const NODE_BATCH_THRESHOLD: i32 = 10;
|
||||||
|
|
||||||
pub struct PendingTiles {
|
|
||||||
pub list: Vec<tiles::TileWithDistance>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PendingTiles {
|
|
||||||
pub fn new_empty() -> Self {
|
|
||||||
Self {
|
|
||||||
list: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, tile_viewbox: &TileViewbox) {
|
|
||||||
self.list.clear();
|
|
||||||
for y in tile_viewbox.interest_rect.1..=tile_viewbox.interest_rect.3 {
|
|
||||||
for x in tile_viewbox.interest_rect.0..=tile_viewbox.interest_rect.2 {
|
|
||||||
let tile = tiles::Tile(x, y);
|
|
||||||
let distance = tiles::manhattan_distance(tile, tile_viewbox.center);
|
|
||||||
self.list.push((x, y, distance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Option<TileWithDistance> {
|
|
||||||
self.list.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sort(&mut self) {
|
|
||||||
self.list.sort_by(|a, b| b.2.cmp(&a.2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NodeRenderState {
|
pub struct NodeRenderState {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
// We use this bool to keep that we've traversed all the children inside this node.
|
// We use this bool to keep that we've traversed all the children inside this node.
|
||||||
|
@ -603,9 +571,8 @@ impl RenderState {
|
||||||
self.pending_tiles.update(&self.tile_viewbox);
|
self.pending_tiles.update(&self.tile_viewbox);
|
||||||
performance::end_measure!("tile_cache");
|
performance::end_measure!("tile_cache");
|
||||||
|
|
||||||
self.pending_nodes = vec![];
|
self.pending_nodes.clear();
|
||||||
// reorder by distance to the center.
|
// reorder by distance to the center.
|
||||||
self.pending_tiles.sort();
|
|
||||||
self.current_tile = None;
|
self.current_tile = None;
|
||||||
self.render_in_progress = true;
|
self.render_in_progress = true;
|
||||||
self.apply_drawing_to_render_canvas(None);
|
self.apply_drawing_to_render_canvas(None);
|
||||||
|
@ -624,7 +591,7 @@ impl RenderState {
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
performance::begin_measure!("process_animation_frame");
|
performance::begin_measure!("process_animation_frame");
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
self.render_shape_tree(tree, modifiers, structure, scale_content, timestamp)?;
|
self.render_shape_tree_partial(tree, modifiers, structure, scale_content, timestamp)?;
|
||||||
self.flush_and_submit();
|
self.flush_and_submit();
|
||||||
|
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
|
@ -638,6 +605,12 @@ impl RenderState {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool {
|
||||||
|
iteration % NODE_BATCH_THRESHOLD == 0
|
||||||
|
&& performance::get_time() - timestamp > MAX_BLOCKING_TIME_MS
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_shape_enter(&mut self, element: &mut Shape, mask: bool) {
|
pub fn render_shape_enter(&mut self, element: &mut Shape, mask: bool) {
|
||||||
// Masked groups needs two rendering passes, the first one rendering
|
// Masked groups needs two rendering passes, the first one rendering
|
||||||
// the content and the second one rendering the mask so we need to do
|
// the content and the second one rendering the mask so we need to do
|
||||||
|
@ -729,40 +702,15 @@ impl RenderState {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_shape_tree(
|
pub fn render_shape_tree_partial_uncached(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut HashMap<Uuid, &mut Shape>,
|
tree: &mut HashMap<Uuid, &mut Shape>,
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
scale_content: &HashMap<Uuid, f32>,
|
scale_content: &HashMap<Uuid, f32>,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(bool, bool), String> {
|
||||||
if !self.render_in_progress {
|
let mut iteration = 0;
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut should_stop = false;
|
|
||||||
|
|
||||||
while !should_stop {
|
|
||||||
if let Some(current_tile) = self.current_tile {
|
|
||||||
if self.surfaces.has_cached_tile_surface(current_tile) {
|
|
||||||
performance::begin_measure!("render_shape_tree::cached");
|
|
||||||
let tile_rect = self.get_current_tile_bounds();
|
|
||||||
self.surfaces
|
|
||||||
.draw_cached_tile_surface(current_tile, tile_rect);
|
|
||||||
performance::end_measure!("render_shape_tree::cached");
|
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
|
||||||
debug::render_workspace_current_tile(
|
|
||||||
self,
|
|
||||||
"Cached".to_string(),
|
|
||||||
current_tile,
|
|
||||||
tile_rect,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
performance::begin_measure!("render_shape_tree::uncached");
|
|
||||||
let mut i = 0;
|
|
||||||
let mut is_empty = true;
|
let mut is_empty = true;
|
||||||
while let Some(node_render_state) = self.pending_nodes.pop() {
|
while let Some(node_render_state) = self.pending_nodes.pop() {
|
||||||
let NodeRenderState {
|
let NodeRenderState {
|
||||||
|
@ -824,9 +772,7 @@ impl RenderState {
|
||||||
transformed_element.to_mut().apply_transform(modifier);
|
transformed_element.to_mut().apply_transform(modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_visible = transformed_element
|
let is_visible = transformed_element.extrect().intersects(self.render_area)
|
||||||
.extrect()
|
|
||||||
.intersects(self.render_area)
|
|
||||||
&& !transformed_element.hidden
|
&& !transformed_element.hidden
|
||||||
&& !transformed_element.visually_insignificant(self.get_scale());
|
&& !transformed_element.visually_insignificant(self.get_scale());
|
||||||
|
|
||||||
|
@ -861,11 +807,10 @@ impl RenderState {
|
||||||
});
|
});
|
||||||
|
|
||||||
if element.is_recursive() {
|
if element.is_recursive() {
|
||||||
let children_clip_bounds = node_render_state
|
let children_clip_bounds =
|
||||||
.get_children_clip_bounds(element, modifiers.get(&element.id));
|
node_render_state.get_children_clip_bounds(element, modifiers.get(&element.id));
|
||||||
|
|
||||||
let mut children_ids =
|
let mut children_ids = modified_children_ids(element, structure.get(&element.id));
|
||||||
modified_children_ids(element, structure.get(&element.id));
|
|
||||||
|
|
||||||
// Z-index ordering on Layouts
|
// Z-index ordering on Layouts
|
||||||
if element.has_layout() {
|
if element.has_layout() {
|
||||||
|
@ -888,12 +833,51 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We try to avoid doing too many calls to get_time
|
// We try to avoid doing too many calls to get_time
|
||||||
if i % NODE_BATCH_THRESHOLD == 0
|
if self.should_stop_rendering(iteration, timestamp) {
|
||||||
&& performance::get_time() - timestamp > MAX_BLOCKING_TIME_MS
|
return Ok((is_empty, true));
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
i += 1;
|
iteration += 1;
|
||||||
|
}
|
||||||
|
Ok((is_empty, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_shape_tree_partial(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut HashMap<Uuid, &mut Shape>,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
scale_content: &HashMap<Uuid, f32>,
|
||||||
|
timestamp: i32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut should_stop = false;
|
||||||
|
while !should_stop {
|
||||||
|
if let Some(current_tile) = self.current_tile {
|
||||||
|
if self.surfaces.has_cached_tile_surface(current_tile) {
|
||||||
|
performance::begin_measure!("render_shape_tree::cached");
|
||||||
|
let tile_rect = self.get_current_tile_bounds();
|
||||||
|
self.surfaces
|
||||||
|
.draw_cached_tile_surface(current_tile, tile_rect);
|
||||||
|
performance::end_measure!("render_shape_tree::cached");
|
||||||
|
|
||||||
|
if self.options.is_debug_visible() {
|
||||||
|
debug::render_workspace_current_tile(
|
||||||
|
self,
|
||||||
|
"Cached".to_string(),
|
||||||
|
current_tile,
|
||||||
|
tile_rect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
performance::begin_measure!("render_shape_tree::uncached");
|
||||||
|
let (is_empty, early_return) = self.render_shape_tree_partial_uncached(
|
||||||
|
tree,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
|
scale_content,
|
||||||
|
timestamp,
|
||||||
|
)?;
|
||||||
|
if early_return {
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
performance::end_measure!("render_shape_tree::uncached");
|
performance::end_measure!("render_shape_tree::uncached");
|
||||||
let tile_rect = self.get_current_tile_bounds();
|
let tile_rect = self.get_current_tile_bounds();
|
||||||
|
@ -920,9 +904,7 @@ impl RenderState {
|
||||||
|
|
||||||
// If we finish processing every node rendering is complete
|
// If we finish processing every node rendering is complete
|
||||||
// let's check if there are more pending nodes
|
// let's check if there are more pending nodes
|
||||||
if let Some(next_tile_with_distance) = self.pending_tiles.pop() {
|
if let Some(next_tile) = self.pending_tiles.pop() {
|
||||||
let (x, y, _) = next_tile_with_distance;
|
|
||||||
let next_tile = tiles::Tile(x, y);
|
|
||||||
self.update_render_context(next_tile);
|
self.update_render_context(next_tile);
|
||||||
|
|
||||||
if !self.surfaces.has_cached_tile_surface(next_tile) {
|
if !self.surfaces.has_cached_tile_surface(next_tile) {
|
||||||
|
|
|
@ -11,6 +11,22 @@ pub struct Tile(pub i32, pub i32);
|
||||||
pub struct TileRect(pub i32, pub i32, pub i32, pub i32);
|
pub struct TileRect(pub i32, pub i32, pub i32, pub i32);
|
||||||
|
|
||||||
impl TileRect {
|
impl TileRect {
|
||||||
|
pub fn width(&self) -> i32 {
|
||||||
|
self.2 - self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> i32 {
|
||||||
|
self.3 - self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center_x(&self) -> i32 {
|
||||||
|
self.0 + self.width() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center_y(&self) -> i32 {
|
||||||
|
self.1 + self.height() / 2
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, tile: &Tile) -> bool {
|
pub fn contains(&self, tile: &Tile) -> bool {
|
||||||
tile.0 >= self.0 && tile.1 >= self.1 && tile.0 <= self.2 && tile.1 <= self.3
|
tile.0 >= self.0 && tile.1 >= self.1 && tile.0 <= self.2 && tile.1 <= self.3
|
||||||
}
|
}
|
||||||
|
@ -44,15 +60,8 @@ impl TileViewbox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TileWithDistance = (i32, i32, i32);
|
|
||||||
|
|
||||||
pub const TILE_SIZE: f32 = 512.;
|
pub const TILE_SIZE: f32 = 512.;
|
||||||
|
|
||||||
// @see https://en.wikipedia.org/wiki/Taxicab_geometry
|
|
||||||
pub fn manhattan_distance(a: Tile, b: Tile) -> i32 {
|
|
||||||
(a.0 - b.0).abs() + (a.1 - b.1).abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_tile_dimensions() -> skia::ISize {
|
pub fn get_tile_dimensions() -> skia::ISize {
|
||||||
(TILE_SIZE as i32, TILE_SIZE as i32).into()
|
(TILE_SIZE as i32, TILE_SIZE as i32).into()
|
||||||
}
|
}
|
||||||
|
@ -151,3 +160,75 @@ impl TileHashMap {
|
||||||
self.index.clear();
|
self.index.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VIEWPORT_DEFAULT_CAPACITY: usize = 24 * 12;
|
||||||
|
|
||||||
|
// This structure keeps the list of tiles that are in the pending list, the
|
||||||
|
// ones that are going to be rendered.
|
||||||
|
pub struct PendingTiles {
|
||||||
|
pub list: Vec<Tile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingTiles {
|
||||||
|
pub fn new_empty() -> Self {
|
||||||
|
Self {
|
||||||
|
list: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, tile_viewbox: &TileViewbox) {
|
||||||
|
self.list.clear();
|
||||||
|
|
||||||
|
let columns = tile_viewbox.interest_rect.width();
|
||||||
|
let rows = tile_viewbox.interest_rect.height();
|
||||||
|
|
||||||
|
let total = columns * rows;
|
||||||
|
|
||||||
|
let mut cx = tile_viewbox.interest_rect.center_x();
|
||||||
|
let mut cy = tile_viewbox.interest_rect.center_y();
|
||||||
|
|
||||||
|
let ratio = (columns as f32 / rows as f32).ceil() as i32;
|
||||||
|
|
||||||
|
let mut direction_current = 0;
|
||||||
|
let mut direction_total_x = ratio;
|
||||||
|
let mut direction_total_y = 1;
|
||||||
|
let mut direction = 0;
|
||||||
|
let mut current = 0;
|
||||||
|
|
||||||
|
self.list.push(Tile(cx, cy));
|
||||||
|
while current < total {
|
||||||
|
match direction {
|
||||||
|
0 => cx += 1,
|
||||||
|
1 => cy += 1,
|
||||||
|
2 => cx -= 1,
|
||||||
|
3 => cy -= 1,
|
||||||
|
_ => unreachable!("Invalid direction"),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.list.push(Tile(cx, cy));
|
||||||
|
|
||||||
|
direction_current += 1;
|
||||||
|
let direction_total = if direction % 2 == 0 {
|
||||||
|
direction_total_x
|
||||||
|
} else {
|
||||||
|
direction_total_y
|
||||||
|
};
|
||||||
|
|
||||||
|
if direction_current == direction_total {
|
||||||
|
if direction % 2 == 0 {
|
||||||
|
direction_total_x += 1;
|
||||||
|
} else {
|
||||||
|
direction_total_y += 1;
|
||||||
|
}
|
||||||
|
direction = (direction + 1) % 4;
|
||||||
|
direction_current = 0;
|
||||||
|
}
|
||||||
|
current += 1;
|
||||||
|
}
|
||||||
|
self.list.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Option<Tile> {
|
||||||
|
self.list.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue