mirror of
https://github.com/penpot/penpot.git
synced 2025-02-21 06:16:28 -05:00
wip
This commit is contained in:
parent
106b357692
commit
cfc6f7e5c1
6 changed files with 356 additions and 43 deletions
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
19
render-wasm/src/render/surfaces.rs
Normal file
19
render-wasm/src/render/surfaces.rs
Normal 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)
|
||||
}
|
||||
}
|
215
render-wasm/src/render/tiles.rs
Normal file
215
render-wasm/src/render/tiles.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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? */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue