mirror of
https://github.com/penpot/penpot.git
synced 2026-09-06 12:09:24 +00:00
♻️ Change how rendering spiral is generated
This commit is contained in:
parent
fc7748fc84
commit
0817f13340
@ -80,6 +80,9 @@ function copy_artifacts {
|
|||||||
|
|
||||||
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js;
|
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js;
|
||||||
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
|
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
|
||||||
|
if [ -f target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm.map ]; then
|
||||||
|
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm.map $DEST/$BUILD_NAME.wasm.map;
|
||||||
|
fi
|
||||||
|
|
||||||
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js;
|
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js;
|
||||||
|
|
||||||
|
|||||||
@ -233,7 +233,7 @@ pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn render(_: i32) -> Result<()> {
|
pub extern "C" fn render(timestamp: i32) -> Result<()> {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.rebuild_touched_tiles();
|
state.rebuild_touched_tiles();
|
||||||
// Drain the throttled modifier-tile invalidation accumulated
|
// Drain the throttled modifier-tile invalidation accumulated
|
||||||
@ -248,7 +248,7 @@ pub extern "C" fn render(_: i32) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
.start_render_loop(performance::get_time())
|
.start_render_loop(timestamp)
|
||||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -260,7 +260,7 @@ pub extern "C" fn render_sync() -> Result<()> {
|
|||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.rebuild_tiles();
|
state.rebuild_tiles();
|
||||||
state
|
state
|
||||||
.render_sync(performance::get_time())
|
.render_sync(0)
|
||||||
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -286,7 +286,7 @@ pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()
|
|||||||
|
|
||||||
state.rebuild_tiles_from(Some(&id));
|
state.rebuild_tiles_from(Some(&id));
|
||||||
state
|
state
|
||||||
.render_sync_shape(&id, performance::get_time())
|
.render_sync_shape(&id, 0)
|
||||||
.map_err(|e| Error::RecoverableError(e.to_string()))?;
|
.map_err(|e| Error::RecoverableError(e.to_string()))?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -534,7 +534,7 @@ impl RenderState {
|
|||||||
options.dpr_viewport_interest_area_threshold,
|
options.dpr_viewport_interest_area_threshold,
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
pending_tiles: PendingTiles::new_empty(),
|
pending_tiles: PendingTiles::new(),
|
||||||
nested_fills: vec![],
|
nested_fills: vec![],
|
||||||
nested_blurs: vec![],
|
nested_blurs: vec![],
|
||||||
nested_shadows: vec![],
|
nested_shadows: vec![],
|
||||||
|
|||||||
@ -8,12 +8,10 @@ pub use text_editor::*;
|
|||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::render::RenderState;
|
use crate::render::RenderState;
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::{modifiers::grid_layout::grid_cell_data, Shape};
|
||||||
use crate::tiles;
|
use crate::tiles;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
use crate::shapes::modifiers::grid_layout::grid_cell_data;
|
|
||||||
|
|
||||||
/// This struct holds the state of the Rust application between JS calls.
|
/// This struct holds the state of the Rust application between JS calls.
|
||||||
///
|
///
|
||||||
/// It is created by [init] and passed to the other exported functions.
|
/// It is created by [init] and passed to the other exported functions.
|
||||||
|
|||||||
@ -22,43 +22,95 @@ impl Tile {
|
|||||||
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 empty() -> Self {
|
||||||
|
Self(0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn x1(&self) -> i32 {
|
pub fn x1(&self) -> i32 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn y1(&self) -> i32 {
|
pub fn y1(&self) -> i32 {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn x2(&self) -> i32 {
|
pub fn x2(&self) -> i32 {
|
||||||
self.2
|
self.2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn y2(&self) -> i32 {
|
pub fn y2(&self) -> i32 {
|
||||||
self.3
|
self.3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn left(&self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn top(&self) -> i32 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn right(&self) -> i32 {
|
||||||
|
self.2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bottom(&self) -> i32 {
|
||||||
|
self.3
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn x(&self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn y(&self) -> i32 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn width(&self) -> i32 {
|
pub fn width(&self) -> i32 {
|
||||||
self.x2() - self.x1()
|
self.x2() - self.x1()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn half_width(&self) -> i32 {
|
||||||
|
self.width() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn height(&self) -> i32 {
|
pub fn height(&self) -> i32 {
|
||||||
self.y2() - self.y1()
|
self.y2() - self.y1()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center_x(&self) -> i32 {
|
#[inline]
|
||||||
self.x1() + self.width() / 2
|
pub fn half_height(&self) -> i32 {
|
||||||
|
self.height() / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn center_x(&self) -> i32 {
|
||||||
|
self.x() + self.half_width()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn center_y(&self) -> i32 {
|
pub fn center_y(&self) -> i32 {
|
||||||
self.y1() + self.height() / 2
|
self.y() + self.half_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, tile: &Tile) -> bool {
|
pub fn contains(&self, tile: &Tile) -> bool {
|
||||||
tile.x() >= self.x1()
|
tile.x() >= self.left()
|
||||||
&& tile.y() >= self.y1()
|
&& tile.y() >= self.top()
|
||||||
&& tile.x() <= self.x2()
|
&& tile.x() <= self.right()
|
||||||
&& tile.y() <= self.y2()
|
&& tile.y() <= self.bottom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,43 +247,70 @@ impl TileHashMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VIEWPORT_DEFAULT_CAPACITY: usize = 24 * 12;
|
const VIEWPORT_DEFAULT_CAPACITY: usize = 24 * 12;
|
||||||
|
const VIEWPORT_SPIRAL_DEFAULT_CAPACITY: usize = 64;
|
||||||
|
|
||||||
// This structure keeps the list of tiles that are in the pending list, the
|
// This structure keeps the list of tiles that are in the pending list, the
|
||||||
// ones that are going to be rendered.
|
// ones that are going to be rendered.
|
||||||
pub struct PendingTiles {
|
pub struct PendingTiles {
|
||||||
pub list: Vec<Tile>,
|
pub list: Vec<Tile>,
|
||||||
|
pub spiral: Vec<Tile>,
|
||||||
|
pub spiral_rect: TileRect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingTiles {
|
impl PendingTiles {
|
||||||
pub fn new_empty() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
list: Vec::with_capacity(VIEWPORT_DEFAULT_CAPACITY),
|
||||||
|
spiral: Vec::with_capacity(VIEWPORT_SPIRAL_DEFAULT_CAPACITY),
|
||||||
|
spiral_rect: TileRect::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate tiles ordered by distance to the center (closest processed first).
|
// Generate tiles in spiral order from center
|
||||||
fn generate_spiral(rect: &TileRect) -> Vec<Tile> {
|
fn generate_spiral(columns: usize, rows: usize) -> Vec<Tile> {
|
||||||
let cx = rect.center_x();
|
let total = columns * rows;
|
||||||
let cy = rect.center_y();
|
let mut result = Vec::with_capacity(total);
|
||||||
|
let mut cx = 0;
|
||||||
|
let mut cy = 0;
|
||||||
|
|
||||||
// TileRect is inclusive (x1..=x2, y1..=y2).
|
let ratio = (columns as f32 / rows as f32).ceil() as i32;
|
||||||
let mut tiles = Vec::new();
|
|
||||||
for x in rect.x1()..=rect.x2() {
|
let mut direction_current = 0;
|
||||||
for y in rect.y1()..=rect.y2() {
|
let mut direction_total_x = ratio;
|
||||||
tiles.push(Tile(x, y));
|
let mut direction_total_y = 1;
|
||||||
|
let mut direction = 0;
|
||||||
|
|
||||||
|
result.push(Tile(cx, cy));
|
||||||
|
while result.len() < total {
|
||||||
|
match direction {
|
||||||
|
0 => cx += 1,
|
||||||
|
1 => cy += 1,
|
||||||
|
2 => cx -= 1,
|
||||||
|
3 => cy -= 1,
|
||||||
|
_ => unreachable!("Invalid direction"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.reverse();
|
||||||
// We pop() from the end, so keep nearest-to-center tiles at the end.
|
result
|
||||||
tiles.sort_unstable_by(|a, b| {
|
|
||||||
let da = (a.x() - cx).abs() + (a.y() - cy).abs();
|
|
||||||
let db = (b.x() - cx).abs() + (b.y() - cy).abs();
|
|
||||||
da.cmp(&db)
|
|
||||||
.then_with(|| a.x().cmp(&b.x()))
|
|
||||||
.then_with(|| a.y().cmp(&b.y()))
|
|
||||||
});
|
|
||||||
tiles.reverse();
|
|
||||||
tiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, tile_viewbox: &TileViewbox, surfaces: &Surfaces, only_visible: bool) {
|
pub fn update(&mut self, tile_viewbox: &TileViewbox, surfaces: &Surfaces, only_visible: bool) {
|
||||||
@ -247,7 +326,18 @@ impl PendingTiles {
|
|||||||
} else {
|
} else {
|
||||||
&tile_viewbox.interest_rect
|
&tile_viewbox.interest_rect
|
||||||
};
|
};
|
||||||
let spiral = Self::generate_spiral(spiral_rect);
|
|
||||||
|
self.spiral_rect = *spiral_rect;
|
||||||
|
|
||||||
|
// We do not regenerate spiral if the spiral_rect
|
||||||
|
// doesn't change. The spiral_rect is based on the
|
||||||
|
// viewbox so, if the viewbox doesn't change
|
||||||
|
// the spiral should not change.
|
||||||
|
let total = (spiral_rect.width() * spiral_rect.height()) as usize;
|
||||||
|
if self.spiral.len() < total {
|
||||||
|
self.spiral =
|
||||||
|
Self::generate_spiral(spiral_rect.width() as usize, spiral_rect.height() as usize);
|
||||||
|
}
|
||||||
|
|
||||||
// Partition tiles into 4 priority groups (highest priority = processed last due to pop()):
|
// Partition tiles into 4 priority groups (highest priority = processed last due to pop()):
|
||||||
// 1. visible + cached (fastest - just blit from cache)
|
// 1. visible + cached (fastest - just blit from cache)
|
||||||
@ -259,7 +349,9 @@ impl PendingTiles {
|
|||||||
let mut interest_cached = Vec::new();
|
let mut interest_cached = Vec::new();
|
||||||
let mut interest_uncached = Vec::new();
|
let mut interest_uncached = Vec::new();
|
||||||
|
|
||||||
for tile in spiral {
|
let center_tile = Tile(spiral_rect.center_x(), spiral_rect.center_y());
|
||||||
|
for spiral_tile in self.spiral.iter() {
|
||||||
|
let tile = Tile(spiral_tile.0 + center_tile.0, spiral_tile.1 + center_tile.1);
|
||||||
let is_visible = tile_viewbox.visible_rect.contains(&tile);
|
let is_visible = tile_viewbox.visible_rect.contains(&tile);
|
||||||
let is_cached = surfaces.has_cached_tile_surface(tile);
|
let is_cached = surfaces.has_cached_tile_surface(tile);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user