0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-21 06:16:28 -05:00
This commit is contained in:
Aitor Moreno 2025-02-20 10:45:59 +01:00
parent 106b357692
commit cfc6f7e5c1
6 changed files with 356 additions and 43 deletions

View file

@ -113,7 +113,9 @@ pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) {
#[no_mangle]
pub extern "C" fn set_view_zoom(zoom: f32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
state.render_state().viewbox.set_zoom(zoom);
let render_state = state.render_state();
render_state.viewbox.set_zoom(zoom);
render_state.invalidate_tiles();
}
#[no_mangle]
@ -188,9 +190,7 @@ pub extern "C" fn set_shape_bool_type(raw_bool_type: u8) {
#[no_mangle]
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) {
let state = unsafe { STATE.as_mut() }.expect("Got an invalid state pointer");
if let Some(shape) = state.current_shape() {
shape.set_selrect(left, top, right, bottom);
}
state.set_selrect_for_current_shape(left, top, right, bottom);
}
#[no_mangle]

View file

@ -2,7 +2,7 @@ use skia_safe as skia;
use std::collections::HashMap;
use uuid::Uuid;
use crate::math::{self, Rect};
use crate::math;
use crate::view::Viewbox;
use skia::{Contains, Matrix};
@ -15,6 +15,8 @@ mod images;
mod options;
mod shadows;
mod strokes;
mod tiles;
mod surfaces;
use crate::shapes::{Kind, Shape};
use cache::CachedSurfaceImage;
@ -61,6 +63,7 @@ pub(crate) struct RenderState {
pub shadow_surface: skia::Surface,
pub overlay_surface: skia::Surface,
pub debug_surface: skia::Surface,
pub debug_font: skia::Font,
pub font_provider: skia::textlayout::TypefaceFontProvider,
pub cached_surface_image: Option<CachedSurfaceImage>,
pub viewbox: Viewbox,
@ -73,7 +76,7 @@ pub(crate) struct RenderState {
// Stack of nodes pending to be rendered.
pub pending_nodes: Vec<NodeRenderState>,
pub render_complete: bool,
pub tile_shapes: HashMap<String, Vec<Shape>>
pub tiles: tiles::Tiles,
}
impl RenderState {
@ -102,6 +105,12 @@ impl RenderState {
.expect("Failed to load font");
font_provider.register_typeface(default_font, "robotomono-regular");
let debug_typeface = font_provider
.match_family_style("robotomono-regular", skia::FontStyle::default())
.unwrap();
let debug_font = skia::Font::new(debug_typeface, 10.0);
let tiles = tiles::Tiles::new(surfaces::SurfaceProvider::new(&mut final_surface, tiles::get_tile_dimensions()));
RenderState {
gpu_state,
final_surface,
@ -110,6 +119,7 @@ impl RenderState {
shadow_surface,
drawing_surface,
debug_surface,
debug_font,
cached_surface_image: None,
font_provider,
options: RenderOptions::default(),
@ -120,6 +130,7 @@ impl RenderState {
render_in_progress: false,
pending_nodes: vec![],
render_complete: true,
tiles
}
}
@ -428,7 +439,7 @@ impl RenderState {
}
if self.options.is_debug_visible() {
self.render_debug();
debug::render(self);
}
debug::render_wasm_label(self);
@ -476,22 +487,23 @@ impl RenderState {
Ok(())
}
fn render_debug(&mut self) {
debug::render(self);
}
pub fn render_shape_tree_in_area(
pub fn render_shape_tree_tile(
&mut self,
tree: &mut HashMap<Uuid, Shape>,
modifiers: &HashMap<Uuid, Matrix>,
timestamp: i32,
area: Rect
tile: (i32, i32)
) -> Result<(), String> {
if !self.render_in_progress {
return Ok(());
// If the tile is empty or it doesn't exists
if !self.tiles.has_tile_at(tile) {
return Ok(())
}
// TODO: We should have a hashmap of grid elements.
if let Some(shapes) = self.tiles.get_tile_at(tile) {
for shape_id in shapes.iter() {
}
}
Ok(())
}
@ -520,6 +532,9 @@ impl RenderState {
.to_string(),
)?;
// 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
// shape tree is rendered. Maybe could this be called render_full or is_full_render?
let render_complete = self.viewbox.area.contains(element.bounds());
if visited_children {
if !visited_mask {
@ -664,4 +679,12 @@ impl RenderState {
self.render_in_progress = false;
Ok(())
}
pub fn update_tile_for(&mut self, shape: &Shape) {
self.tiles.update_tile_for(self.viewbox, &shape);
}
pub fn invalidate_tiles(&mut self) {
self.tiles.invalidate_tiles();
}
}

View file

@ -1,70 +1,113 @@
use crate::shapes::Shape;
use skia_safe as skia;
use super::RenderState;
use super::math::Rect;
use super::{tiles, RenderState};
const DEBUG_SCALE: f32 = 0.2;
fn get_debug_rect(rect: Rect) -> Rect {
skia::Rect::from_xywh(100. + rect.x() * DEBUG_SCALE, 100. + rect.y() * DEBUG_SCALE, rect.width() * DEBUG_SCALE, rect.height() * DEBUG_SCALE)
}
fn render_debug_view(render_state: &mut RenderState) {
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(skia::Color::from_argb(255, 255, 0, 255));
paint.set_color(skia::Color::from_rgb(255, 0, 255));
paint.set_stroke_width(1.);
let mut scaled_rect = render_state.viewbox.area.clone();
let x = 100. + scaled_rect.x() * 0.2;
let y = 100. + scaled_rect.y() * 0.2;
let width = scaled_rect.width() * 0.2;
let height = scaled_rect.height() * 0.2;
scaled_rect.set_xywh(x, y, width, height);
let rect = get_debug_rect(render_state.viewbox.area.clone());
render_state
.debug_surface
.canvas()
.draw_rect(scaled_rect, &paint);
.draw_rect(rect, &paint);
}
pub fn render_wasm_label(render_state: &mut RenderState) {
let canvas = render_state.render_surface.canvas();
let skia::ISize { width, height } = canvas.base_layer_size();
let p = skia::Point::new(width as f32 - 100.0, height as f32 - 25.0);
let mut paint = skia::Paint::default();
paint.set_color(skia::Color::from_argb(100, 0, 0, 0));
let font_provider = &render_state.font_provider;
let typeface = font_provider
.match_family_style("robotomono-regular", skia::FontStyle::default())
.unwrap();
let str = if render_state.options.is_debug_visible() { "WASM RENDERER (DEBUG)" } else { "WASM RENDERER" };
let (scalar, _) = render_state.debug_font.measure_str(str, None);
let p = skia::Point::new(width as f32 - 25.0 - scalar, height as f32 - 25.0);
let font = skia::Font::new(typeface, 10.0);
canvas.draw_str("WASM RENDERER", p, &font, &paint);
canvas.draw_str(str, p, &render_state.debug_font, &paint);
}
pub fn render_debug_shape(render_state: &mut RenderState, element: &Shape, intersected: bool) {
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(if intersected {
skia::Color::from_argb(255, 255, 255, 0)
skia::Color::from_rgb(255, 255, 0)
} else {
skia::Color::from_argb(255, 0, 255, 255)
skia::Color::from_rgb(0, 255, 255)
});
paint.set_stroke_width(1.);
let mut scaled_rect = element.bounds();
let x = 100. + scaled_rect.x() * 0.2;
let y = 100. + scaled_rect.y() * 0.2;
let width = scaled_rect.width() * 0.2;
let height = scaled_rect.height() * 0.2;
scaled_rect.set_xywh(x, y, width, height);
let rect = get_debug_rect(element.bounds());
render_state
.debug_surface
.canvas()
.draw_rect(scaled_rect, &paint);
.draw_rect(rect, &paint);
}
// Renders the tiles in the viewbox
pub fn render_debug_viewbox_tiles(render_state: &mut RenderState) {
let canvas = render_state.debug_surface.canvas();
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(skia::Color::from_rgb(255, 0, 127));
paint.set_stroke_width(1.);
let tile_size = tiles::get_tile_size(render_state.viewbox);
let (sx, sy, ex, ey) = tiles::get_tiles_for_rect(render_state.viewbox.area, tile_size);
for y in sy..=ey {
for x in sx..=ex {
let rect = Rect::from_xywh(x as f32 * tile_size, y as f32 * tile_size, tile_size, tile_size);
let debug_rect = get_debug_rect(rect);
let p = skia::Point::new(debug_rect.x(), debug_rect.y() - 1.);
let str = format!("{}:{}", x, y);
canvas.draw_str(str, p, &render_state.debug_font, &paint);
canvas.draw_rect(&debug_rect, &paint);
}
}
}
pub fn render_debug_tiles(render_state: &mut RenderState) {
let canvas = render_state.debug_surface.canvas();
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(skia::Color::from_rgb(127, 0, 255));
paint.set_stroke_width(1.);
let tile_size = tiles::get_tile_size(render_state.viewbox);
let (sx, sy, ex, ey) = tiles::get_tiles_for_rect(render_state.viewbox.area, tile_size);
for y in sy..=ey {
for x in sx..=ex {
let tile = (x, y);
let shape_count = render_state.tiles.get_tile_shape_count(tile);
if shape_count == 0 {
continue;
}
let rect = Rect::from_xywh(x as f32 * tile_size, y as f32 * tile_size, tile_size, tile_size);
let debug_rect = get_debug_rect(rect);
let p = skia::Point::new(debug_rect.x(), debug_rect.y() - 1.);
let str = format!("{}:{} {}", x, y, shape_count);
canvas.draw_str(str, p, &render_state.debug_font, &paint);
canvas.draw_rect(&debug_rect, &paint);
}
}
}
pub fn render(render_state: &mut RenderState) {
let paint = skia::Paint::default();
render_debug_view(render_state);
render_debug_viewbox_tiles(render_state);
render_debug_tiles(render_state);
render_state.debug_surface.draw(
&mut render_state.render_surface.canvas(),
(0.0, 0.0),

View file

@ -0,0 +1,19 @@
use skia_safe as skia;
pub struct SurfaceProvider<'a> {
base_surface: &'a mut skia::Surface,
dim: skia::ISize
}
impl<'a> SurfaceProvider<'a> {
pub fn new(base_surface: &'a mut skia::Surface, dim: skia::ISize) -> Self {
SurfaceProvider {
base_surface,
dim
}
}
pub fn provide(&mut self) -> Option<skia::Surface> {
self.base_surface.new_surface_with_dimensions(self.dim)
}
}

View file

@ -0,0 +1,215 @@
use skia_safe as skia;
use std::collections::{HashMap, HashSet};
use uuid::Uuid;
use crate::view::Viewbox;
use crate::render::surfaces::SurfaceProvider;
use super::Shape;
pub type Tile = (i32, i32);
const TILE_SIZE: f32 = 512.;
pub fn get_tile_dimensions() -> skia::ISize {
(TILE_SIZE as i32, TILE_SIZE as i32).into()
}
pub fn get_tiles_for_rect(rect: skia::Rect, tile_size: f32) -> (i32, i32, i32, i32) {
// start
let sx = (rect.left / tile_size).floor() as i32;
let sy = (rect.top / tile_size).floor() as i32;
// end
let ex = (rect.right / tile_size).floor() as i32;
let ey = (rect.bottom / tile_size).floor() as i32;
(sx, sy, ex, ey)
}
pub fn get_tile_size(viewbox: Viewbox) -> f32 {
1. / viewbox.zoom * TILE_SIZE
}
pub struct TileSurfaceCache<'a> {
provider: SurfaceProvider<'a>,
grid: HashMap<Tile, &'a mut skia::Surface>
}
impl<'a> TileSurfaceCache<'a> {
pub fn new(provider: SurfaceProvider<'a>) -> Self {
TileSurfaceCache {
provider,
grid: HashMap::new()
}
}
pub fn has(&mut self, tile: Tile) -> bool {
return self.grid.contains_key(&tile);
}
pub fn get_or_create(&mut self, tile: Tile) -> &skia::Surface {
let surface = &mut self.provider.provide().unwrap();
self.grid.insert(tile, surface);
surface
}
pub fn set(&mut self, tile: Tile, surface: skia::Surface) {
self.grid.insert(tile, surface);
}
pub fn remove(&mut self, tile: Tile) -> bool {
if !self.grid.contains_key(&tile) {
return false;
}
self.grid.remove(&tile);
true
}
pub fn clear(&mut self) {
self.grid.clear();
}
}
// This structure is usseful to keep all the shape uuids by shape id.
pub struct TileHashMap {
grid: HashMap<Tile, HashSet<Uuid>>,
index: HashMap<Uuid, HashSet<Tile>>
}
impl TileHashMap {
pub fn new() -> Self {
TileHashMap {
grid: HashMap::new(),
index: HashMap::new()
}
}
pub fn has_shapes_at(&mut self, tile: Tile) -> bool {
return self.grid.contains_key(&tile);
}
pub fn get_shapes_at(&mut self, tile: Tile) -> Option<&HashSet<Uuid>> {
return self.grid.get(&tile);
}
pub fn get_tiles_of(&mut self, shape_id: Uuid) -> Option<&HashSet<Tile>> {
self.index.get(&shape_id)
}
pub fn add_shape_at(&mut self, tile: Tile, shape_id: Uuid) {
if !self.grid.contains_key(&tile) {
self.grid.insert(tile, HashSet::new());
}
if !self.index.contains_key(&shape_id) {
self.index.insert(shape_id, HashSet::new());
}
let tile_set = self.grid.get_mut(&tile).unwrap();
tile_set.insert(shape_id);
let index_set = self.index.get_mut(&shape_id).unwrap();
index_set.insert(tile);
}
pub fn remove_shape(&mut self, shape_id: Uuid) {
if let Some(index_set) = self.index.get(&shape_id) {
for tile in index_set {
if let Some(tile_set) = self.grid.get_mut(tile) {
tile_set.remove(&shape_id);
}
}
}
}
pub fn remove_shape_at(&mut self, tile: Tile, shape_id: Uuid) -> bool {
if !self.grid.contains_key(&tile) {
return false;
}
let tile_set = self.grid.get_mut(&tile).unwrap();
tile_set.remove(&shape_id);
let index_set = self.index.get_mut(&shape_id).unwrap();
index_set.remove(&tile);
true
}
pub fn remove_all_shapes_at(&mut self, tile: Tile) -> bool {
if !self.grid.contains_key(&tile) {
return false;
}
let tile_set = self.grid.get_mut(&tile).unwrap();
for shape_id in tile_set.iter() {
let index_set = self.index.get_mut(&shape_id).unwrap();
index_set.clear();
}
tile_set.clear();
true
}
pub fn clear(&mut self) {
self.grid.clear();
self.index.clear();
}
}
pub struct Tiles {
surfaces: TileSurfaceCache,
shapes: TileHashMap
}
impl Tiles {
pub fn new(provider: SurfaceProvider) -> Self {
Tiles {
surfaces: TileSurfaceCache::new(provider),
shapes: TileHashMap::new()
}
}
pub fn invalidate_surfaces(&mut self) {
self.surfaces.clear();
}
pub fn invalidate_shapes(&mut self) {
self.shapes.clear();
}
pub fn invalidate_tiles(&mut self) {
self.surfaces.clear();
self.shapes.clear();
}
pub fn get_or_create_surface_at(&mut self, tile: Tile) -> &skia::Surface {
self.surfaces.get_or_create(tile)
}
pub fn get_tile_shape_count(&mut self, tile: Tile) -> usize {
if let Some(shapes) = self.shapes.get_shapes_at(tile) {
return shapes.len();
}
// the tile is empty
0
}
pub fn has_tile_at(&mut self, tile: Tile) -> bool {
self.shapes.has_shapes_at(tile)
}
pub fn get_tile_at(&mut self, tile: Tile) -> Option<&HashSet<Uuid>> {
self.shapes.get_shapes_at(tile)
}
pub fn update_tile_for(&mut self, viewbox: Viewbox, shape: &Shape) {
let tile_size = get_tile_size(viewbox);
let (sx, sy, ex, ey) = get_tiles_for_rect(shape.selrect, tile_size);
for x in sx..=ex {
for y in sy..=ey {
let tile = (x, y);
self.shapes.add_shape_at(tile, shape.id);
}
}
}
}

View file

@ -71,4 +71,17 @@ impl<'a> State<'a> {
pub fn set_background_color(&mut self, color: skia::Color) {
self.render_state.set_background_color(color);
}
pub fn set_selrect_for_current_shape(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
match self.current_shape.as_mut() {
Some(shape) => {
shape.set_selrect(left, top, right, bottom);
// We don't need to update the tile for the root shape.
if !shape.id.is_nil() {
self.render_state.update_tile_for(&shape);
}
},
None => { /* TODO: Esto debería lanzar una excepción? */ }
}
}
}