mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🎉 Save shape data in rust memory
This commit is contained in:
45
render-wasm/Cargo.lock
generated
45
render-wasm/Cargo.lock
generated
@@ -96,22 +96,6 @@ version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "emscripten-functions"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62c026cc030b24957ca45d9555f9fa241d6b3a01d725cd98a25924de249b840a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"emscripten-functions-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "emscripten-functions-sys"
|
||||
version = "4.1.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65715a5f07b03636d7cd5508a45d1b62486840cb7d91a66564a73f1d7aa70b79"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@@ -150,6 +134,17 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl"
|
||||
version = "0.14.0"
|
||||
@@ -386,10 +381,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
name = "render"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"emscripten-functions",
|
||||
"emscripten-functions-sys",
|
||||
"gl",
|
||||
"skia-safe",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -554,6 +548,21 @@ version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
|
||||
@@ -11,10 +11,9 @@ name = "render_wasm"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
emscripten-functions = "0.2.3"
|
||||
emscripten-functions-sys = "4.1.67"
|
||||
gl = "0.14.0"
|
||||
skia-safe = { version = "0.78.2", features = ["gl"] }
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
|
||||
@@ -1,83 +1,161 @@
|
||||
pub mod render;
|
||||
pub mod shapes;
|
||||
pub mod state;
|
||||
pub mod utils;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use skia_safe as skia;
|
||||
use uuid::Uuid;
|
||||
|
||||
use render::State;
|
||||
use crate::shapes::Shape;
|
||||
use crate::state::State;
|
||||
use crate::utils::uuid_from_u32_quartet;
|
||||
|
||||
static mut STATE: Option<Box<State>> = None;
|
||||
|
||||
/// This is called from JS after the WebGL context has been created.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn init(width: i32, height: i32) -> Box<render::State> {
|
||||
let mut gpu_state = render::create_gpu_state();
|
||||
let surface = render::create_surface(&mut gpu_state, width, height);
|
||||
|
||||
let state = State::new(gpu_state, surface);
|
||||
|
||||
Box::new(state)
|
||||
pub extern "C" fn init(width: i32, height: i32) {
|
||||
let state_box = Box::new(State::with_capacity(width, height, 2048));
|
||||
unsafe {
|
||||
STATE = Some(state_box);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is called from JS when the window is resized.
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i32) {
|
||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||
let surface = render::create_surface(&mut state.gpu_state, width, height);
|
||||
pub unsafe extern "C" fn resize_surface(width: i32, height: i32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
let surface = render::create_surface(&mut state.render_state.gpu_state, width, height);
|
||||
state.set_surface(surface);
|
||||
}
|
||||
|
||||
/// Draws a rect at the specified coordinates with the give ncolor
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn draw_rect(state: *mut State, x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||
pub unsafe extern "C" fn draw_rect(x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
let r = skia::Rect::new(x1, y1, x2, y2);
|
||||
render::render_rect(&mut state.surface, r, skia::Color::RED);
|
||||
render::render_rect(&mut state.render_state.surface, r, skia::Color::RED);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn draw_all_shapes(state: *mut State, zoom: f32, pan_x: f32, pan_y: f32) {
|
||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||
pub unsafe extern "C" fn draw_all_shapes(zoom: f32, pan_x: f32, pan_y: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
|
||||
reset_canvas(state);
|
||||
scale(state, zoom, zoom);
|
||||
translate(state, pan_x, pan_y);
|
||||
reset_canvas();
|
||||
scale(zoom, zoom);
|
||||
translate(pan_x, pan_y);
|
||||
|
||||
shapes::draw_all(state);
|
||||
render::render_all(state);
|
||||
|
||||
flush(state);
|
||||
flush();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn flush(state: *mut State) {
|
||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||
pub unsafe extern "C" fn flush() {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
state
|
||||
.render_state
|
||||
.gpu_state
|
||||
.context
|
||||
.flush_and_submit_surface(&mut state.surface, None);
|
||||
.flush_and_submit_surface(&mut state.render_state.surface, None);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn translate(state: *mut State, dx: f32, dy: f32) {
|
||||
(*state).surface.canvas().translate((dx, dy));
|
||||
pub unsafe extern "C" fn translate(dx: f32, dy: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
state.render_state.surface.canvas().translate((dx, dy));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn scale(state: *mut State, sx: f32, sy: f32) {
|
||||
(*state).surface.canvas().scale((sx, sy));
|
||||
pub unsafe extern "C" fn scale(sx: f32, sy: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
state.render_state.surface.canvas().scale((sx, sy));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn reset_canvas(state: *mut State) {
|
||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
||||
state.surface.canvas().clear(skia_safe::Color::TRANSPARENT);
|
||||
state.surface.canvas().reset_matrix();
|
||||
flush(state);
|
||||
pub extern "C" fn reset_canvas() {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
state
|
||||
.render_state
|
||||
.surface
|
||||
.canvas()
|
||||
.clear(skia_safe::Color::TRANSPARENT);
|
||||
state.render_state.surface.canvas().reset_matrix();
|
||||
}
|
||||
|
||||
pub fn get_or_create_shape<'a>(shapes: &'a mut HashMap<Uuid, Shape>, id: Uuid) -> &'a mut Shape {
|
||||
if !shapes.contains_key(&id) {
|
||||
let new_shape = Shape::new(id);
|
||||
shapes.insert(id, new_shape);
|
||||
}
|
||||
|
||||
shapes.get_mut(&id).unwrap()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn shapes_buffer() -> *mut shapes::Shape {
|
||||
let ptr = shapes::SHAPES_BUFFER.as_mut_ptr();
|
||||
return ptr;
|
||||
pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||
state.current_id = Some(id);
|
||||
let shapes = &mut state.shapes;
|
||||
state.current_shape = Some(get_or_create_shape(shapes, id));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_selrect(x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
|
||||
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||
shape.selrect.x1 = x1;
|
||||
shape.selrect.y1 = y1;
|
||||
shape.selrect.x2 = x2;
|
||||
shape.selrect.y2 = y2;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_rotation(rotation: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||
shape.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_x(x: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||
let width = shape.selrect.x2 - shape.selrect.x1;
|
||||
shape.selrect.x1 = x;
|
||||
shape.selrect.x2 = x + width;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_y(y: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||
let height = shape.selrect.y2 - shape.selrect.y1;
|
||||
shape.selrect.y1 = y;
|
||||
shape.selrect.y2 = y + height;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn set_shape_transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) {
|
||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||
shape.transform.a = a;
|
||||
shape.transform.b = b;
|
||||
shape.transform.c = c;
|
||||
shape.transform.d = d;
|
||||
shape.transform.e = e;
|
||||
shape.transform.f = f;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
use skia_safe as skia;
|
||||
use skia_safe::gpu::{self, gl::FramebufferInfo, DirectContext};
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
extern "C" {
|
||||
pub fn emscripten_GetProcAddress(
|
||||
name: *const ::std::os::raw::c_char,
|
||||
) -> *const ::std::os::raw::c_void;
|
||||
}
|
||||
|
||||
pub struct GpuState {
|
||||
pub(crate) struct GpuState {
|
||||
pub context: DirectContext,
|
||||
framebuffer_info: FramebufferInfo,
|
||||
}
|
||||
|
||||
/// This struct holds the state of the Rust application between JS calls.
|
||||
///
|
||||
/// It is created by [init] and passed to the other exported functions. Note that rust-skia data
|
||||
/// structures are not thread safe, so a state must not be shared between different Web Workers.
|
||||
pub struct State {
|
||||
pub(crate) struct RenderState {
|
||||
pub gpu_state: GpuState,
|
||||
pub surface: skia::Surface,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(gpu_state: GpuState, surface: skia::Surface) -> Self {
|
||||
State { gpu_state, surface }
|
||||
}
|
||||
|
||||
pub fn set_surface(&mut self, surface: skia::Surface) {
|
||||
self.surface = surface;
|
||||
impl RenderState {
|
||||
pub fn new(width: i32, height: i32) -> RenderState {
|
||||
let mut gpu_state = create_gpu_state();
|
||||
let surface = create_surface(&mut gpu_state, width, height);
|
||||
RenderState { gpu_state, surface }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,3 +80,35 @@ pub(crate) fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color:
|
||||
paint.set_anti_alias(true);
|
||||
surface.canvas().draw_rect(rect, &paint);
|
||||
}
|
||||
|
||||
pub(crate) fn render_all(state: &mut State) {
|
||||
for shape in state.shapes.values() {
|
||||
let r = skia::Rect::new(
|
||||
shape.selrect.x1,
|
||||
shape.selrect.y1,
|
||||
shape.selrect.x2,
|
||||
shape.selrect.y2,
|
||||
);
|
||||
|
||||
state.render_state.surface.canvas().save();
|
||||
|
||||
// Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc
|
||||
let mut matrix = skia::Matrix::new_identity();
|
||||
let (translate_x, translate_y) = shape.translation();
|
||||
let (scale_x, scale_y) = shape.scale();
|
||||
let (skew_x, skew_y) = shape.skew();
|
||||
|
||||
matrix.set_all(scale_x, skew_x, translate_x, skew_y, scale_y, translate_y, 0., 0., 1.);
|
||||
|
||||
let mut center = r.center();
|
||||
matrix.post_translate(center);
|
||||
center.negate();
|
||||
matrix.pre_translate(center);
|
||||
|
||||
state.render_state.surface.canvas().concat(&matrix);
|
||||
|
||||
render_rect(&mut state.render_state.surface, r, skia::Color::RED);
|
||||
|
||||
state.render_state.surface.canvas().restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,84 @@
|
||||
use crate::render::{render_rect, State};
|
||||
use skia_safe as skia;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Selrect {
|
||||
pub enum Kind {
|
||||
None,
|
||||
Text,
|
||||
Path,
|
||||
SVGRaw,
|
||||
Image,
|
||||
Circle,
|
||||
Rect,
|
||||
Bool,
|
||||
Group,
|
||||
Frame,
|
||||
}
|
||||
|
||||
pub struct Point {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Rect {
|
||||
pub x1: f32,
|
||||
pub y1: f32,
|
||||
pub x2: f32,
|
||||
pub y2: f32,
|
||||
}
|
||||
|
||||
pub type Shape = Selrect; // temp
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Matrix {
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub c: f32,
|
||||
pub d: f32,
|
||||
pub e: f32,
|
||||
pub f: f32,
|
||||
}
|
||||
|
||||
pub static mut SHAPES_BUFFER: [Shape; 2048] = [Selrect {
|
||||
x1: 0.0,
|
||||
y1: 0.0,
|
||||
x2: 0.0,
|
||||
y2: 0.0,
|
||||
}; 2048];
|
||||
|
||||
pub(crate) fn draw_all(state: &mut State) {
|
||||
let shapes;
|
||||
unsafe {
|
||||
shapes = SHAPES_BUFFER.iter();
|
||||
}
|
||||
|
||||
for shape in shapes {
|
||||
let r = skia::Rect::new(shape.x1, shape.y1, shape.x2, shape.y2);
|
||||
render_rect(&mut state.surface, r, skia::Color::RED);
|
||||
impl Matrix {
|
||||
pub fn identity() -> Self {
|
||||
Self {
|
||||
a: 1.,
|
||||
b: 0.,
|
||||
c: 0.,
|
||||
d: 1.,
|
||||
e: 0.,
|
||||
f: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Shape {
|
||||
pub id: Uuid,
|
||||
pub kind: Kind,
|
||||
pub selrect: Rect,
|
||||
pub transform: Matrix,
|
||||
pub rotation: f32,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn new(id: Uuid) -> Self {
|
||||
Self {
|
||||
id,
|
||||
kind: Kind::Rect,
|
||||
selrect: Rect::default(),
|
||||
transform: Matrix::identity(),
|
||||
rotation: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translation(&self) -> (f32, f32) {
|
||||
(self.transform.e, self.transform.f)
|
||||
}
|
||||
|
||||
pub fn scale(&self) -> (f32, f32) {
|
||||
(self.transform.a, self.transform.d)
|
||||
}
|
||||
|
||||
pub fn skew(&self) -> (f32, f32) {
|
||||
(self.transform.c, self.transform.b)
|
||||
}
|
||||
}
|
||||
|
||||
36
render-wasm/src/state.rs
Normal file
36
render-wasm/src/state.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use skia_safe as skia;
|
||||
use std::collections::HashMap;
|
||||
use std::vec::Vec;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::render::RenderState;
|
||||
use crate::shapes::Shape;
|
||||
|
||||
/// This struct holds the state of the Rust application between JS calls.
|
||||
///
|
||||
/// It is created by [init] and passed to the other exported functions.
|
||||
/// Note that rust-skia data structures are not thread safe, so a state
|
||||
/// must not be shared between different Web Workers.
|
||||
pub(crate) struct State<'a> {
|
||||
pub render_state: RenderState,
|
||||
pub current_id: Option<Uuid>,
|
||||
pub current_shape: Option<&'a mut Shape>,
|
||||
pub shapes: HashMap<Uuid, Shape>,
|
||||
pub display_list: Vec<Uuid>,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn with_capacity(width: i32, height: i32, capacity: usize) -> Self {
|
||||
State {
|
||||
render_state: RenderState::new(width, height),
|
||||
current_id: None,
|
||||
current_shape: None,
|
||||
shapes: HashMap::with_capacity(capacity),
|
||||
display_list: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_surface(&mut self, surface: skia::Surface) {
|
||||
self.render_state.surface = surface;
|
||||
}
|
||||
}
|
||||
8
render-wasm/src/utils.rs
Normal file
8
render-wasm/src/utils.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid
|
||||
{
|
||||
let hi: u64 = ((a as u64) << 32) | b as u64;
|
||||
let lo: u64 = ((c as u64) << 32) | d as u64;
|
||||
Uuid::from_u64_pair(hi, lo)
|
||||
}
|
||||
Reference in New Issue
Block a user