mirror of
https://github.com/penpot/penpot.git
synced 2025-05-31 22:51:37 +02:00
🐛 Fix paths performance in new render
This commit is contained in:
parent
8afd217a80
commit
96d44e0631
4 changed files with 183 additions and 22 deletions
|
@ -92,6 +92,74 @@ export function addShapeSolidFill(argb) {
|
||||||
Module._add_shape_fill();
|
Module._add_shape_fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addShapeSolidStroleFill(argb) {
|
||||||
|
const ptr = allocBytes(160);
|
||||||
|
const heap = getHeapU32();
|
||||||
|
const dv = new DataView(heap.buffer);
|
||||||
|
dv.setUint8(ptr, 0x00, true);
|
||||||
|
dv.setUint32(ptr + 4, argb, true);
|
||||||
|
Module._add_shape_stroke_fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializePathAttrs(svgAttrs) {
|
||||||
|
return Object.entries(svgAttrs).reduce((acc, [key, value]) => {
|
||||||
|
return acc + key + '\0' + value + '\0';
|
||||||
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function draw_star(x, y, width, height) {
|
||||||
|
const len = 11; // 1 MOVE + 9 LINE + 1 CLOSE
|
||||||
|
const ptr = allocBytes(len * 28);
|
||||||
|
const heap = getHeapU32();
|
||||||
|
const dv = new DataView(heap.buffer);
|
||||||
|
|
||||||
|
const cx = x + width / 2;
|
||||||
|
const cy = y + height / 2;
|
||||||
|
const outerRadius = Math.min(width, height) / 2;
|
||||||
|
const innerRadius = outerRadius * 0.4;
|
||||||
|
|
||||||
|
const star = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const angle = Math.PI / 5 * i - Math.PI / 2;
|
||||||
|
const r = i % 2 === 0 ? outerRadius : innerRadius;
|
||||||
|
const px = cx + r * Math.cos(angle);
|
||||||
|
const py = cy + r * Math.sin(angle);
|
||||||
|
star.push([px, py]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
// MOVE to first point
|
||||||
|
dv.setUint16(ptr + offset + 0, 1, true); // MOVE
|
||||||
|
dv.setFloat32(ptr + offset + 20, star[0][0], true);
|
||||||
|
dv.setFloat32(ptr + offset + 24, star[0][1], true);
|
||||||
|
offset += 28;
|
||||||
|
|
||||||
|
// LINE to remaining points
|
||||||
|
for (let i = 1; i < star.length; i++) {
|
||||||
|
dv.setUint16(ptr + offset + 0, 2, true); // LINE
|
||||||
|
dv.setFloat32(ptr + offset + 20, star[i][0], true);
|
||||||
|
dv.setFloat32(ptr + offset + 24, star[i][1], true);
|
||||||
|
offset += 28;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLOSE the path
|
||||||
|
dv.setUint16(ptr + offset + 0, 4, true); // CLOSE
|
||||||
|
|
||||||
|
Module._set_shape_path_content();
|
||||||
|
|
||||||
|
const str = serializePathAttrs({
|
||||||
|
"fill": "none",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round",
|
||||||
|
});
|
||||||
|
const size = str.length;
|
||||||
|
offset = allocBytes(size);
|
||||||
|
Module.stringToUTF8(str, offset, size);
|
||||||
|
Module._set_shape_path_attrs(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function setShapeChildren(shapeIds) {
|
export function setShapeChildren(shapeIds) {
|
||||||
const offset = allocBytes(shapeIds.length * 16);
|
const offset = allocBytes(shapeIds.length * 16);
|
||||||
const heap = getHeapU32();
|
const heap = getHeapU32();
|
||||||
|
|
83
frontend/resources/wasm-playground/paths.html
Normal file
83
frontend/resources/wasm-playground/paths.html
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>WASM + WebGL2 Canvas</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #111;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<script type="module">
|
||||||
|
import initWasmModule from '/js/render_wasm.js';
|
||||||
|
import {
|
||||||
|
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||||
|
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent, draw_star,
|
||||||
|
addShapeSolidStroleFill
|
||||||
|
} from './js/lib.js';
|
||||||
|
|
||||||
|
const canvas = document.getElementById("canvas");
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
const shapes = 1000;
|
||||||
|
|
||||||
|
initWasmModule().then(Module => {
|
||||||
|
init(Module);
|
||||||
|
assignCanvas(canvas);
|
||||||
|
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
|
||||||
|
Module._set_view(1, 0, 0);
|
||||||
|
Module._init_shapes_pool(shapes + 1);
|
||||||
|
setupInteraction(canvas);
|
||||||
|
|
||||||
|
const children = [];
|
||||||
|
for (let i = 0; i < shapes; i++) {
|
||||||
|
const uuid = crypto.randomUUID();
|
||||||
|
children.push(uuid);
|
||||||
|
|
||||||
|
useShape(uuid);
|
||||||
|
Module._set_parent(0, 0, 0, 0);
|
||||||
|
Module._set_shape_type(4);
|
||||||
|
const x1 = getRandomInt(0, canvas.width);
|
||||||
|
const y1 = getRandomInt(0, canvas.height);
|
||||||
|
const width = getRandomInt(20, 200);
|
||||||
|
const height = getRandomInt(20, 200);
|
||||||
|
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
|
||||||
|
draw_star(x1, y1, width, height);
|
||||||
|
|
||||||
|
const color = getRandomColor();
|
||||||
|
const argb = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||||
|
addShapeSolidFill(argb)
|
||||||
|
|
||||||
|
Module._add_shape_center_stroke(10, 0, 0, 0);
|
||||||
|
const argb2 = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||||
|
addShapeSolidStroleFill(argb2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
useShape("00000000-0000-0000-0000-000000000000");
|
||||||
|
setShapeChildren(children);
|
||||||
|
|
||||||
|
performance.mark('render:begin');
|
||||||
|
Module._render(Date.now());
|
||||||
|
performance.mark('render:end');
|
||||||
|
const { duration } = performance.measure('render', 'render:begin', 'render:end');
|
||||||
|
// alert(`render time: ${duration.toFixed(2)}ms`);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -26,13 +26,13 @@
|
||||||
import initWasmModule from '/js/render_wasm.js';
|
import initWasmModule from '/js/render_wasm.js';
|
||||||
import {
|
import {
|
||||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||||
getRandomFloat, useShape, setShapeChildren, setupInteraction
|
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStroleFill
|
||||||
} from './js/lib.js';
|
} from './js/lib.js';
|
||||||
|
|
||||||
const canvas = document.getElementById("canvas");
|
const canvas = document.getElementById("canvas");
|
||||||
canvas.width = window.innerWidth;
|
canvas.width = window.innerWidth;
|
||||||
canvas.height = window.innerHeight;
|
canvas.height = window.innerHeight;
|
||||||
const shapes = 100;
|
const shapes = 1000;
|
||||||
|
|
||||||
initWasmModule().then(Module => {
|
initWasmModule().then(Module => {
|
||||||
init(Module);
|
init(Module);
|
||||||
|
@ -59,6 +59,10 @@
|
||||||
const color = getRandomColor();
|
const color = getRandomColor();
|
||||||
const argb = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
const argb = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||||
addShapeSolidFill(argb)
|
addShapeSolidFill(argb)
|
||||||
|
|
||||||
|
Module._add_shape_center_stroke(10, 0, 0, 0);
|
||||||
|
const argb2 = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||||
|
addShapeSolidStroleFill(argb2);
|
||||||
}
|
}
|
||||||
|
|
||||||
useShape("00000000-0000-0000-0000-000000000000");
|
useShape("00000000-0000-0000-0000-000000000000");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use skia_safe::{self as skia, image, Matrix, RRect, Rect};
|
use skia_safe::{self as skia, image, Matrix, RRect, Rect};
|
||||||
|
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
|
@ -339,11 +340,11 @@ impl RenderState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone so we don't change the value in the global state
|
// We don't want to change the value in the global state
|
||||||
let mut shape = shape.clone();
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
|
|
||||||
if let Some(modifiers) = modifiers {
|
if let Some(modifiers) = modifiers {
|
||||||
shape.apply_transform(modifiers);
|
shape.to_mut().apply_transform(modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
let center = shape.center();
|
let center = shape.center();
|
||||||
|
@ -365,7 +366,7 @@ impl RenderState {
|
||||||
match dom_result {
|
match dom_result {
|
||||||
Ok(dom) => {
|
Ok(dom) => {
|
||||||
dom.render(self.surfaces.canvas(SurfaceId::Fills));
|
dom.render(self.surfaces.canvas(SurfaceId::Fills));
|
||||||
shape.set_svg(dom);
|
shape.to_mut().set_svg(dom);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error parsing SVG. Error: {}", e);
|
eprintln!("Error parsing SVG. Error: {}", e);
|
||||||
|
@ -777,19 +778,24 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !node_render_state.id.is_nil() {
|
if !node_render_state.id.is_nil() {
|
||||||
// If we didn't visited_children this shape, then we need to do
|
let mut transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
||||||
let mut transformed_element = element.clone();
|
|
||||||
if let Some(modifier) = modifiers.get(&node_id) {
|
if let Some(modifier) = modifiers.get(&node_id) {
|
||||||
transformed_element.apply_transform(modifier);
|
transformed_element.to_mut().apply_transform(modifier);
|
||||||
}
|
}
|
||||||
if !transformed_element.extrect().intersects(self.render_area)
|
|
||||||
|| transformed_element.hidden()
|
let is_visible = transformed_element
|
||||||
|| transformed_element.visually_insignificant(self.get_scale())
|
.extrect()
|
||||||
{
|
.intersects(self.render_area)
|
||||||
debug::render_debug_shape(self, &transformed_element, false);
|
&& !transformed_element.hidden
|
||||||
|
&& !transformed_element.visually_insignificant(self.get_scale());
|
||||||
|
|
||||||
|
if self.options.is_debug_visible() {
|
||||||
|
debug::render_debug_shape(self, &transformed_element, is_visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_visible {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
|
||||||
debug::render_debug_shape(self, &transformed_element, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -957,10 +963,10 @@ impl RenderState {
|
||||||
let mut nodes = vec![Uuid::nil()];
|
let mut nodes = vec![Uuid::nil()];
|
||||||
while let Some(shape_id) = nodes.pop() {
|
while let Some(shape_id) = nodes.pop() {
|
||||||
if let Some(shape) = tree.get_mut(&shape_id) {
|
if let Some(shape) = tree.get_mut(&shape_id) {
|
||||||
let mut shape = shape.clone();
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
if shape_id != Uuid::nil() {
|
if shape_id != Uuid::nil() {
|
||||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||||
shape.apply_transform(modifier);
|
shape.to_mut().apply_transform(modifier);
|
||||||
}
|
}
|
||||||
self.update_tile_for(&shape);
|
self.update_tile_for(&shape);
|
||||||
} else {
|
} else {
|
||||||
|
@ -987,10 +993,10 @@ impl RenderState {
|
||||||
let mut nodes = vec![Uuid::nil()];
|
let mut nodes = vec![Uuid::nil()];
|
||||||
while let Some(shape_id) = nodes.pop() {
|
while let Some(shape_id) = nodes.pop() {
|
||||||
if let Some(shape) = tree.get_mut(&shape_id) {
|
if let Some(shape) = tree.get_mut(&shape_id) {
|
||||||
let mut shape = shape.clone();
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
if shape_id != Uuid::nil() {
|
if shape_id != Uuid::nil() {
|
||||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||||
shape.apply_transform(modifier);
|
shape.to_mut().apply_transform(modifier);
|
||||||
}
|
}
|
||||||
self.update_tile_for(&shape);
|
self.update_tile_for(&shape);
|
||||||
}
|
}
|
||||||
|
@ -1011,8 +1017,8 @@ impl RenderState {
|
||||||
) {
|
) {
|
||||||
for (uuid, matrix) in modifiers {
|
for (uuid, matrix) in modifiers {
|
||||||
if let Some(shape) = tree.get_mut(uuid) {
|
if let Some(shape) = tree.get_mut(uuid) {
|
||||||
let mut shape: Shape = shape.clone();
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
shape.apply_transform(matrix);
|
shape.to_mut().apply_transform(matrix);
|
||||||
self.update_tile_for(&shape);
|
self.update_tile_for(&shape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue