🎉 Save shape data in rust memory

This commit is contained in:
Alejandro Alonso
2024-11-12 11:09:50 +01:00
parent 48909dc3c4
commit 65ee2f9081
13 changed files with 479 additions and 297 deletions

45
render-wasm/Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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() {

View File

@@ -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();
}
}

View File

@@ -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
View 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
View 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)
}