diff --git a/frontend/resources/wasm-playground/js/lib.js b/frontend/resources/wasm-playground/js/lib.js index 5be649e7b..0f27cf83a 100644 --- a/frontend/resources/wasm-playground/js/lib.js +++ b/frontend/resources/wasm-playground/js/lib.js @@ -92,6 +92,74 @@ export function addShapeSolidFill(argb) { 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) { const offset = allocBytes(shapeIds.length * 16); const heap = getHeapU32(); diff --git a/frontend/resources/wasm-playground/paths.html b/frontend/resources/wasm-playground/paths.html new file mode 100644 index 000000000..dd0cc13ca --- /dev/null +++ b/frontend/resources/wasm-playground/paths.html @@ -0,0 +1,83 @@ + + + + + WASM + WebGL2 Canvas + + + + + + + diff --git a/frontend/resources/wasm-playground/rects.html b/frontend/resources/wasm-playground/rects.html index 322cadd64..92df83d54 100644 --- a/frontend/resources/wasm-playground/rects.html +++ b/frontend/resources/wasm-playground/rects.html @@ -26,13 +26,13 @@ import initWasmModule from '/js/render_wasm.js'; import { init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor, - getRandomFloat, useShape, setShapeChildren, setupInteraction + getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStroleFill } from './js/lib.js'; const canvas = document.getElementById("canvas"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; - const shapes = 100; + const shapes = 1000; initWasmModule().then(Module => { init(Module); @@ -59,6 +59,10 @@ 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"); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 3c9d93383..3c59c04fd 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1,6 +1,7 @@ use skia_safe::{self as skia, image, Matrix, RRect, Rect}; use crate::uuid::Uuid; +use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use crate::performance; @@ -339,11 +340,11 @@ impl RenderState { }); } - // Clone so we don't change the value in the global state - let mut shape = shape.clone(); + // We don't want to change the value in the global state + let mut shape: Cow = Cow::Borrowed(shape); if let Some(modifiers) = modifiers { - shape.apply_transform(modifiers); + shape.to_mut().apply_transform(modifiers); } let center = shape.center(); @@ -365,7 +366,7 @@ impl RenderState { match dom_result { Ok(dom) => { dom.render(self.surfaces.canvas(SurfaceId::Fills)); - shape.set_svg(dom); + shape.to_mut().set_svg(dom); } Err(e) => { eprintln!("Error parsing SVG. Error: {}", e); @@ -777,19 +778,24 @@ impl RenderState { } if !node_render_state.id.is_nil() { - // If we didn't visited_children this shape, then we need to do - let mut transformed_element = element.clone(); + let mut transformed_element: Cow = Cow::Borrowed(element); + 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() - || transformed_element.visually_insignificant(self.get_scale()) - { - debug::render_debug_shape(self, &transformed_element, false); + + let is_visible = transformed_element + .extrect() + .intersects(self.render_area) + && !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; - } else { - debug::render_debug_shape(self, &transformed_element, true); } } @@ -957,10 +963,10 @@ impl RenderState { let mut nodes = vec![Uuid::nil()]; while let Some(shape_id) = nodes.pop() { if let Some(shape) = tree.get_mut(&shape_id) { - let mut shape = shape.clone(); + let mut shape: Cow = Cow::Borrowed(shape); if shape_id != Uuid::nil() { if let Some(modifier) = modifiers.get(&shape_id) { - shape.apply_transform(modifier); + shape.to_mut().apply_transform(modifier); } self.update_tile_for(&shape); } else { @@ -987,10 +993,10 @@ impl RenderState { let mut nodes = vec![Uuid::nil()]; while let Some(shape_id) = nodes.pop() { if let Some(shape) = tree.get_mut(&shape_id) { - let mut shape = shape.clone(); + let mut shape: Cow = Cow::Borrowed(shape); if shape_id != Uuid::nil() { if let Some(modifier) = modifiers.get(&shape_id) { - shape.apply_transform(modifier); + shape.to_mut().apply_transform(modifier); } self.update_tile_for(&shape); } @@ -1011,8 +1017,8 @@ impl RenderState { ) { for (uuid, matrix) in modifiers { if let Some(shape) = tree.get_mut(uuid) { - let mut shape: Shape = shape.clone(); - shape.apply_transform(matrix); + let mut shape: Cow = Cow::Borrowed(shape); + shape.to_mut().apply_transform(matrix); self.update_tile_for(&shape); } }