0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-22 23:06:08 -05:00
This commit is contained in:
Aitor Moreno 2025-02-21 12:30:59 +01:00
parent 067825aaed
commit 425d49aeb3
5 changed files with 194 additions and 72 deletions

View file

@ -74,7 +74,8 @@ pub extern "C" fn set_canvas_background(raw_color: u32) {
#[no_mangle] #[no_mangle]
pub extern "C" fn render(timestamp: i32) { pub extern "C" fn render(timestamp: i32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
state.start_render_loop(timestamp).expect("Error rendering"); // state.start_render_loop(timestamp).expect("Error rendering");
state.start_render_loop_tiles(timestamp).expect("Error rendering tiles");
} }
#[no_mangle] #[no_mangle]
@ -108,9 +109,13 @@ pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
let render_state = state.render_state(); let render_state = state.render_state();
render_state.invalidate_cache_if_needed(); render_state.invalidate_cache_if_needed();
if zoom != render_state.viewbox.zoom {
render_state.invalidate_tiles();
}
render_state.viewbox.set_all(zoom, x, y); render_state.viewbox.set_all(zoom, x, y);
} }
/*
#[no_mangle] #[no_mangle]
pub extern "C" fn set_view_zoom(zoom: f32) { pub extern "C" fn set_view_zoom(zoom: f32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
@ -124,6 +129,7 @@ pub extern "C" fn set_view_xy(x: f32, y: f32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer"); let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
state.render_state().viewbox.set_pan_xy(x, y); state.render_state().viewbox.set_pan_xy(x, y);
} }
*/
#[no_mangle] #[no_mangle]
pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) { pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {

View file

@ -28,8 +28,8 @@ pub use images::*;
const DEFAULT_FONT_BYTES: &[u8] = const DEFAULT_FONT_BYTES: &[u8] =
include_bytes!("../../frontend/resources/fonts/RobotoMono-Regular.ttf"); include_bytes!("../../frontend/resources/fonts/RobotoMono-Regular.ttf");
const MAX_BLOCKING_TIME_MS: i32 = 32; const MAX_BLOCKING_TIME_MS: i32 = 320000;
const NODE_BATCH_THRESHOLD: i32 = 10; const NODE_BATCH_THRESHOLD: i32 = 100000;
extern "C" { extern "C" {
fn emscripten_run_script(script: *const i8); fn emscripten_run_script(script: *const i8);
@ -72,6 +72,7 @@ pub(crate) struct RenderState {
pub pending_nodes: Vec<NodeRenderState>, pub pending_nodes: Vec<NodeRenderState>,
pub current_tile: tiles::Tile, pub current_tile: tiles::Tile,
pub render_complete: bool, pub render_complete: bool,
pub render_area: Rect,
pub tiles: tiles::Tiles, pub tiles: tiles::Tiles,
} }
@ -79,7 +80,7 @@ impl RenderState {
pub fn new(width: i32, height: i32) -> RenderState { pub fn new(width: i32, height: i32) -> RenderState {
// This needs to be done once per WebGL context. // This needs to be done once per WebGL context.
let mut gpu_state = GpuState::new(); let mut gpu_state = GpuState::new();
let surfaces = Surfaces::new(&mut gpu_state, (width, height)); let mut surfaces = Surfaces::new(&mut gpu_state, (width, height));
let mut font_provider = skia::textlayout::TypefaceFontProvider::new(); let mut font_provider = skia::textlayout::TypefaceFontProvider::new();
let default_font = skia::FontMgr::default() let default_font = skia::FontMgr::default()
.new_from_data(DEFAULT_FONT_BYTES, None) .new_from_data(DEFAULT_FONT_BYTES, None)
@ -97,7 +98,7 @@ impl RenderState {
let debug_font = skia::Font::new(debug_typeface, 10.0); let debug_font = skia::Font::new(debug_typeface, 10.0);
let surface_pool = surfaces::SurfacePool::new(&mut final_surface, tiles::get_tile_dimensions()); let surface_pool = surfaces::SurfacePool::new(&mut surfaces.target, tiles::get_tile_dimensions());
let tiles = tiles::Tiles::new(surface_pool); let tiles = tiles::Tiles::new(surface_pool);
RenderState { RenderState {
@ -113,8 +114,9 @@ impl RenderState {
background_color: skia::Color::TRANSPARENT, background_color: skia::Color::TRANSPARENT,
render_request_id: None, render_request_id: None,
render_in_progress: false, render_in_progress: false,
pending_nodes: vec![],
render_complete: true, render_complete: true,
render_area: Rect::new_empty(),
pending_nodes: vec![],
current_tile: (0, 0), current_tile: (0, 0),
tiles tiles
} }
@ -247,7 +249,10 @@ impl RenderState {
.canvas() .canvas()
.clear(skia::Color::TRANSPARENT); .clear(skia::Color::TRANSPARENT);
self.surfaces.shape.canvas().clear(skia::Color::TRANSPARENT); self.surfaces
.shape
.canvas()
.clear(skia::Color::TRANSPARENT);
} }
pub fn invalidate_cache_if_needed(&mut self) { pub fn invalidate_cache_if_needed(&mut self) {
@ -391,6 +396,7 @@ impl RenderState {
mask: false, mask: false,
}]; }];
self.render_area = self.viewbox.area;
self.render_in_progress = true; self.render_in_progress = true;
self.process_animation_frame(tree, modifiers, timestamp)?; self.process_animation_frame(tree, modifiers, timestamp)?;
self.render_complete = true; self.render_complete = true;
@ -417,45 +423,47 @@ impl RenderState {
modifiers: &HashMap<Uuid, Matrix>, modifiers: &HashMap<Uuid, Matrix>,
timestamp: i32, timestamp: i32,
) -> Result<(), String> { ) -> Result<(), String> {
let area = self.render_area;
if self.render_in_progress { if self.render_in_progress {
self.render_shape_tree(tree, modifiers, timestamp)?; self.render_shape_tree(tree, modifiers, area, timestamp)?;
if self.render_in_progress { // if self.render_in_progress {
if let Some(frame_id) = self.render_request_id { // if let Some(frame_id) = self.render_request_id {
self.cancel_animation_frame(frame_id); // self.cancel_animation_frame(frame_id);
} // }
self.render_request_id = Some(self.request_animation_frame()); // self.render_request_id = Some(self.request_animation_frame());
} // }
} }
// self.render_in_progress can have changed // self.render_in_progress can have changed
if self.render_in_progress { // if self.render_in_progress {
if self.cached_surface_image.is_some() { // if self.cached_surface_image.is_some() {
self.render_from_cache()?; // self.render_from_cache()?;
} // }
return Ok(()); // return Ok(());
} // }
// Chech if cached_surface_image is not set or is invalid // Chech if cached_surface_image is not set or is invalid
if self // if self
.cached_surface_image // .cached_surface_image
.as_ref() // .as_ref()
.map_or(true, |img| img.invalid) // .map_or(true, |img| img.invalid)
{ // {
self.cached_surface_image = Some(CachedSurfaceImage { // self.cached_surface_image = Some(CachedSurfaceImage {
image: self.surfaces.current.image_snapshot(), // image: self.surfaces.current.image_snapshot(),
viewbox: self.viewbox, // viewbox: self.viewbox,
invalid: false, // invalid: false,
has_all_shapes: self.render_complete, // has_all_shapes: self.render_complete,
}); // });
} // }
if self.options.is_debug_visible() { // NOTE: Esto lo he sacado al start_render_loop_tiles
debug::render(self); // if self.options.is_debug_visible() {
} // debug::render(self);
// }
debug::render_wasm_label(self); // debug::render_wasm_label(self);
self.apply_render_to_final_canvas(); // self.apply_render_to_final_canvas();
self.flush(); // self.flush();
Ok(()) Ok(())
} }
@ -500,61 +508,144 @@ impl RenderState {
Ok(()) Ok(())
} }
pub fn start_render_loop_tiles(
&mut self,
tree: &mut HashMap<Uuid, Shape>,
modifiers: &HashMap<Uuid, Matrix>,
timestamp: i32
) -> Result<(), String> {
self.reset_canvas();
self.surfaces.shape.canvas().scale((
self.viewbox.zoom * self.options.dpr(),
self.viewbox.zoom * self.options.dpr(),
));
self.surfaces.shape
.canvas()
.translate((self.viewbox.pan_x, self.viewbox.pan_y));
let (sx, sy, ex, ey) = tiles::get_tiles_for_viewbox(self.viewbox);
/*
// TODO: Instead of rendering only the visible area
// we could apply an offset to the viewbox to render
// more tiles.
sx - interest_delta
sy - interest_delta
ex + interest_delta
ey + interest_delta
*/
for y in sy..=ey {
for x in sx..=ex {
let tile = (x, y);
self.render_in_progress = true;
let _ = self.start_render_loop_tile(tree, modifiers, tile, timestamp);
}
}
self.render_complete = true;
if self.options.is_debug_visible() {
debug::render(self);
}
debug::render_wasm_label(self);
self.apply_render_to_final_canvas();
self.flush();
Ok(())
}
pub fn start_render_loop_tile( pub fn start_render_loop_tile(
&mut self, &mut self,
tree: &mut HashMap<Uuid, Shape>, tree: &mut HashMap<Uuid, Shape>,
modifiers: &HashMap<Uuid, Matrix>, modifiers: &HashMap<Uuid, Matrix>,
timestamp: i32, tile: (i32, i32),
tile: (i32, i32) timestamp: i32
) -> Result<(), String> { ) -> Result<(), String> {
self.current_tile = tile; self.current_tile = tile;
if self.render_in_progress { println!("Renderizando tile {}:{} {}", tile.0, tile.1, self.render_in_progress);
if let Some(frame_id) = self.render_request_id { println!("Area {:?}", self.render_area);
self.cancel_animation_frame(frame_id);
}
}
self.reset_canvas(); // NOTE: No debería afectar
self.drawing_surface.canvas().scale(( // if self.render_in_progress {
self.viewbox.zoom * self.options.dpr(), // if let Some(frame_id) = self.render_request_id {
self.viewbox.zoom * self.options.dpr(), // self.cancel_animation_frame(frame_id);
)); // }
self.drawing_surface // }
.canvas()
.translate((self.viewbox.pan_x, self.viewbox.pan_y));
// TODO: Check if this should use `self.pending_nodes.push` or the // NOTE: I've moved this to start_render_loop_tiles
// assignment. // self.reset_canvas();
self.pending_nodes = vec![NodeRenderState { // self.surfaces.shape.canvas().scale((
id: Uuid::nil(), // self.viewbox.zoom * self.options.dpr(),
visited_children: false, // self.viewbox.zoom * self.options.dpr(),
clip_bounds: None, // ));
visited_mask: false, // self.surfaces.shape
mask: false, // .canvas()
}]; // .translate((self.viewbox.pan_x, self.viewbox.pan_y));
self.render_in_progress = true;
self.process_animation_frame(tree, modifiers, timestamp)?;
self.render_complete = true;
// If the tile is empty or it doesn't exists // If the tile is empty or it doesn't exists
if !self.tiles.has_tile_at(tile) { if !self.tiles.has_tile_at(tile) {
println!("Salimos porque el tile está vacío {}:{}", tile.0, tile.1);
return Ok(()) return Ok(())
} }
// TODO: Check if this should use `self.pending_nodes.push` or the
// assignment.
// self.pending_nodes = vec![NodeRenderState {
// id: Uuid::nil(),
// visited_children: false,
// clip_bounds: None,
// visited_mask: false,
// mask: false,
// }];
if let Some(shapes) = self.tiles.get_tile_at(tile) { if let Some(shapes) = self.tiles.get_tile_at(tile) {
self.pending_nodes = vec![NodeRenderState {
id: Uuid::nil(),
visited_children: false,
clip_bounds: None,
visited_mask: false,
mask: false,
}];
/*
for shape_id in shapes.iter() { for shape_id in shapes.iter() {
let element = tree.get_mut(&shape_id).ok_or(
"Error: Element with root_id {node_render_state.id} not found in the tree."
.to_string(),
)?;
let children_clip_bounds = if element.is_recursive() {
(!element.id.is_nil() & element.clip()).then(|| {
let bounds = element.selrect();
let mut transform = element.transform;
transform.post_translate(bounds.center());
transform.pre_translate(-bounds.center());
if let Some(modifiers) = modifiers.get(&element.id) {
transform.post_concat(&modifiers);
}
let corners = match element.kind {
Kind::Rect(_, corners) => corners,
_ => None,
};
(bounds, corners, transform)
})
} else {
None
};
self.pending_nodes.push(NodeRenderState { self.pending_nodes.push(NodeRenderState {
id: *shape_id, id: *shape_id,
visited_children: false, visited_children: false,
clip_bounds: None, clip_bounds: children_clip_bounds,
visited_mask: false, visited_mask: false,
mask: false, mask: false,
}) })
} }
*/
} }
self.render_area = tiles::get_tile_rect(self.viewbox, tile);
self.render_shape_tree(tree, modifiers, self.render_area, timestamp)?;
// self.render_in_progress = true;
// self.process_animation_frame(tree, modifiers, timestamp)?;
// self.render_complete = true;
Ok(()) Ok(())
} }
@ -617,6 +708,7 @@ impl RenderState {
&mut self, &mut self,
tree: &mut HashMap<Uuid, Shape>, tree: &mut HashMap<Uuid, Shape>,
modifiers: &HashMap<Uuid, Matrix>, modifiers: &HashMap<Uuid, Matrix>,
area: Rect,
timestamp: i32, timestamp: i32,
) -> Result<(), String> { ) -> Result<(), String> {
if !self.render_in_progress { if !self.render_in_progress {
@ -640,7 +732,7 @@ impl RenderState {
// FIXME: I think this name is ambiguous because render_in_progress indicates that the // FIXME: I think this name is ambiguous because render_in_progress indicates that the
// render is still in progress but render_complete indicates that every element in the // render is still in progress but render_complete indicates that every element in the
// shape tree is rendered. Maybe could this be called render_full or is_full_render? // shape tree is rendered. Maybe could this be called render_full or is_full_render?
let render_complete = self.viewbox.area.contains(element.selrect()); let render_complete = area.contains(element.selrect());
if visited_children { if visited_children {
if !visited_mask { if !visited_mask {
match element.kind { match element.kind {
@ -678,7 +770,7 @@ impl RenderState {
// If we didn't visited_children this shape, then we need to do // If we didn't visited_children this shape, then we need to do
if !node_render_state.id.is_nil() { if !node_render_state.id.is_nil() {
if !element.selrect().intersects(self.viewbox.area) || element.hidden() { if !element.selrect().intersects(area) || element.hidden() {
debug::render_debug_shape(self, element, false); debug::render_debug_shape(self, element, false);
self.render_complete = render_complete; self.render_complete = render_complete;
continue; continue;
@ -745,10 +837,12 @@ impl RenderState {
} }
pub fn update_tile_for(&mut self, shape: &Shape) { pub fn update_tile_for(&mut self, shape: &Shape) {
println!("update_tile_for {}", shape.id);
self.tiles.update_tile_for(self.viewbox, &shape); self.tiles.update_tile_for(self.viewbox, &shape);
} }
pub fn invalidate_tiles(&mut self) { pub fn invalidate_tiles(&mut self) {
println!("invalidate_tiles");
self.tiles.invalidate_tiles(); self.tiles.invalidate_tiles();
} }
} }

View file

@ -57,7 +57,7 @@ pub fn render_debug_shape(render_state: &mut RenderState, element: &Shape, inter
// Renders the tiles in the viewbox // Renders the tiles in the viewbox
pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) { pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
let canvas = render_state.debug_surface.canvas(); let canvas = render_state.surfaces.debug.canvas();
let mut paint = skia::Paint::default(); let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke); paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(skia::Color::from_rgb(255, 0, 127)); paint.set_color(skia::Color::from_rgb(255, 0, 127));
@ -78,7 +78,7 @@ pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
} }
pub fn render_debug_tiles(render_state: &mut RenderState) { pub fn render_debug_tiles(render_state: &mut RenderState) {
let canvas = render_state.debug_surface.canvas(); let canvas = render_state.surfaces.debug.canvas();
let mut paint = skia::Paint::default(); let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke); paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(skia::Color::from_rgb(127, 0, 255)); paint.set_color(skia::Color::from_rgb(127, 0, 255));

View file

@ -24,10 +24,27 @@ pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> (i32, i32, i32, i
(sx, sy, ex, ey) (sx, sy, ex, ey)
} }
pub fn get_tiles_for_viewbox(viewbox: Viewbox) -> (i32, i32, i32, i32) {
let tile_size = get_tile_size(viewbox);
get_tiles_for_rect(viewbox.area, tile_size)
}
pub fn get_tile_pos(viewbox: Viewbox, (x, y): Tile) -> (f32, f32) {
(x as f32 * get_tile_size(viewbox), y as f32 * get_tile_size(viewbox))
}
pub fn get_tile_size(viewbox: Viewbox) -> f32 { pub fn get_tile_size(viewbox: Viewbox) -> f32 {
1. / viewbox.zoom * TILE_SIZE 1. / viewbox.zoom * TILE_SIZE
} }
pub fn get_tile_rect(viewbox: Viewbox, tile: Tile) -> skia::Rect {
let (tx, ty) = get_tile_pos(viewbox, tile);
let ts = get_tile_size(viewbox);
skia::Rect::from_xywh(
tx, ty, ts, ts
)
}
pub struct TileSurfaceCache { pub struct TileSurfaceCache {
pool: SurfacePool, pool: SurfacePool,
grid: HashMap<Tile, skia::Surface> grid: HashMap<Tile, skia::Surface>
@ -49,7 +66,7 @@ impl TileSurfaceCache {
let surface = self.pool.allocate()?; let surface = self.pool.allocate()?;
self.grid.insert(tile, surface.clone()); self.grid.insert(tile, surface.clone());
Ok(surface) Ok(surface)
} }
pub fn set(&mut self, tile: Tile, surface: skia::Surface) { pub fn set(&mut self, tile: Tile, surface: skia::Surface) {
self.grid.insert(tile, surface); self.grid.insert(tile, surface);

View file

@ -44,6 +44,11 @@ impl<'a> State<'a> {
Ok(()) Ok(())
} }
pub fn start_render_loop_tiles(&mut self, timestamp: i32) -> Result<(), String> {
self.render_state.start_render_loop_tiles(&mut self.shapes, &self.modifiers, timestamp)?;
Ok(())
}
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> { pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
self.render_state self.render_state
.process_animation_frame(&mut self.shapes, &self.modifiers, timestamp)?; .process_animation_frame(&mut self.shapes, &self.modifiers, timestamp)?;