mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
Compare commits
21 Commits
2.12.0-RC4
...
azazeln28-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbbf3ab41d | ||
|
|
5b26a9a269 | ||
|
|
f56420d327 | ||
|
|
462ac77fcd | ||
|
|
fd3c3d720e | ||
|
|
a20e0c5733 | ||
|
|
26ca7ed566 | ||
|
|
2a58387b02 | ||
|
|
84ad14183e | ||
|
|
0704fa5df6 | ||
|
|
b438f4dfa1 | ||
|
|
4a85a9ac46 | ||
|
|
a77ca32bf8 | ||
|
|
2a7ab93892 | ||
|
|
03e4ff828b | ||
|
|
2ab5487a84 | ||
|
|
f60254e3eb | ||
|
|
4274db11e3 | ||
|
|
ee3bf72034 | ||
|
|
446fc53f00 | ||
|
|
3af70a965d |
@@ -29,33 +29,33 @@
|
||||
const iterations = parseInt(url.searchParams.get('iterations') ?? 1_000, 10);
|
||||
|
||||
function prepare(Module, canvas) {
|
||||
init(Module);
|
||||
assignCanvas(canvas);
|
||||
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
|
||||
Module._set_view(1, 0, 0);
|
||||
setup({
|
||||
instance: Module,
|
||||
canvas
|
||||
})
|
||||
|
||||
const children = [];
|
||||
for (let shape = 0; shape < shapes; shape++) {
|
||||
const uuid = crypto.randomUUID();
|
||||
children.push(uuid);
|
||||
|
||||
useShape(uuid);
|
||||
|
||||
Module._set_parent(0, 0, 0, 0);
|
||||
Module._set_shape_type(3);
|
||||
const x1 = getRandomInt(0, canvas.width);
|
||||
const y1 = getRandomInt(0, canvas.height);
|
||||
const width = getRandomInt(20, 100);
|
||||
const height = getRandomInt(20, 100);
|
||||
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
|
||||
|
||||
const color = getRandomColor();
|
||||
const argb = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||
addShapeSolidFill(argb)
|
||||
children.push(
|
||||
addShape({
|
||||
parent: "00000000-0000-0000-0000-000000000000",
|
||||
type: "rect",
|
||||
selrect: {
|
||||
x: getRandomInt(0, canvas.width),
|
||||
y: getRandomInt(0, canvas.height),
|
||||
width: getRandomInt(20, 100),
|
||||
height: getRandomInt(20, 100)
|
||||
},
|
||||
fills: [{ type: "solid", color: getRandomColor(), opacity: getRandomFloat(0.1, 1.0) }]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
useShape("00000000-0000-0000-0000-000000000000");
|
||||
setShapeChildren(children);
|
||||
addShape({
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
children
|
||||
})
|
||||
}
|
||||
|
||||
function createElement(tag, attribs, children) {
|
||||
|
||||
@@ -27,12 +27,12 @@ export function assignCanvas(canvas) {
|
||||
context.getExtension("WEBGL_debug_renderer_info");
|
||||
|
||||
Module._init(canvas.width, canvas.height);
|
||||
Module._set_render_options(0, 1);
|
||||
Module._set_render_options(1, 1);
|
||||
}
|
||||
|
||||
export function hexToU32ARGB(hex, opacity = 1) {
|
||||
const rgb = parseInt(hex.slice(1), 16);
|
||||
const a = Math.floor(opacity * 0xFF);
|
||||
const a = Math.floor(opacity * 0xff);
|
||||
const argb = (a << 24) | rgb;
|
||||
return argb >>> 0;
|
||||
}
|
||||
@@ -42,9 +42,9 @@ export function getRandomInt(min, max) {
|
||||
}
|
||||
|
||||
export function getRandomColor() {
|
||||
const r = getRandomInt(0, 256).toString(16).padStart(2, '0');
|
||||
const g = getRandomInt(0, 256).toString(16).padStart(2, '0');
|
||||
const b = getRandomInt(0, 256).toString(16).padStart(2, '0');
|
||||
const r = getRandomInt(0, 256).toString(16).padStart(2, "0");
|
||||
const g = getRandomInt(0, 256).toString(16).padStart(2, "0");
|
||||
const b = getRandomInt(0, 256).toString(16).padStart(2, "0");
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
|
||||
@@ -103,12 +103,12 @@ export function addShapeSolidStrokeFill(argb) {
|
||||
|
||||
function serializePathAttrs(svgAttrs) {
|
||||
return Object.entries(svgAttrs).reduce((acc, [key, value]) => {
|
||||
return acc + key + '\0' + value + '\0';
|
||||
}, '');
|
||||
return acc + key + "\0" + value + "\0";
|
||||
}, "");
|
||||
}
|
||||
|
||||
export function draw_star(x, y, width, height) {
|
||||
const len = 11; // 1 MOVE + 9 LINE + 1 CLOSE
|
||||
export function drawStar(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);
|
||||
@@ -120,7 +120,7 @@ export function draw_star(x, y, width, height) {
|
||||
|
||||
const star = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const angle = Math.PI / 5 * i - Math.PI / 2;
|
||||
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);
|
||||
@@ -149,7 +149,7 @@ export function draw_star(x, y, width, height) {
|
||||
Module._set_shape_path_content();
|
||||
|
||||
const str = serializePathAttrs({
|
||||
"fill": "none",
|
||||
fill: "none",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round",
|
||||
});
|
||||
@@ -158,7 +158,6 @@ export function draw_star(x, y, width, height) {
|
||||
Module.stringToUTF8(str, offset, size);
|
||||
Module._set_shape_path_attrs(3);
|
||||
}
|
||||
|
||||
|
||||
export function setShapeChildren(shapeIds) {
|
||||
const offset = allocBytes(shapeIds.length * 16);
|
||||
@@ -176,7 +175,7 @@ export function useShape(id) {
|
||||
Module._use_shape(...buffer);
|
||||
}
|
||||
|
||||
export function set_parent(id) {
|
||||
export function setParent(id) {
|
||||
const buffer = getU32(id);
|
||||
Module._set_parent(...buffer);
|
||||
}
|
||||
@@ -227,8 +226,12 @@ export function setupInteraction(canvas) {
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener("mouseup", () => { isPanning = false; });
|
||||
canvas.addEventListener("mouseout", () => { isPanning = false; });
|
||||
canvas.addEventListener("mouseup", () => {
|
||||
isPanning = false;
|
||||
});
|
||||
canvas.addEventListener("mouseout", () => {
|
||||
isPanning = false;
|
||||
});
|
||||
}
|
||||
|
||||
export function addTextShape(x, y, fontSize, text) {
|
||||
@@ -312,4 +315,57 @@ export function addTextShape(x, y, fontSize, text) {
|
||||
|
||||
// Call the WebAssembly function
|
||||
Module._set_shape_text_content();
|
||||
}
|
||||
}
|
||||
|
||||
export function setup(options) {
|
||||
init(options.instance)
|
||||
assignCanvas(options.canvas)
|
||||
Module._set_canvas_background(hexToU32ARGB(options?.backgroundColor ?? "#FABADA", 1));
|
||||
Module._set_view(options?.zoom ?? 1, options?.x ?? 0, options?.y ?? 0);
|
||||
Module._init_shapes_pool(options.shapes + 1);
|
||||
setupInteraction(options.canvas);
|
||||
}
|
||||
|
||||
function getShapeType(type) {
|
||||
switch (type) {
|
||||
default:
|
||||
case "rect": return 3;
|
||||
}
|
||||
}
|
||||
|
||||
export function addShape(init) {
|
||||
const uuid = init?.id ?? crypto.randomUUID()
|
||||
useShape(uuid);
|
||||
setParent(init?.parent ?? "00000000-0000-0000-0000-000000000000");
|
||||
|
||||
Module._set_shape_type(getShapeType(init?.type));
|
||||
if (init.selrect) {
|
||||
Module._set_shape_selrect(
|
||||
init.selrect.x,
|
||||
init.selrect.y,
|
||||
init.selrect.x + init.selrect.width,
|
||||
init.selrect.y + init.selrect.height,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(init?.fills)) {
|
||||
for (const fill of init.fills) {
|
||||
const argb = hexToU32ARGB(fill.color, fill.opacity);
|
||||
addShapeSolidFill(argb);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(init?.strokes)) {
|
||||
for (const stroke of init.strokes) {
|
||||
Module._add_shape_center_stroke(stroke.width, 0, 0, 0);
|
||||
const argb = hexToU32ARGB(stroke.color, stroke.opacity);
|
||||
addShapeSolidStrokeFill(argb);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(init?.children)) {
|
||||
setShapeChildren(init.children);
|
||||
}
|
||||
|
||||
return uuid
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
<script type="module">
|
||||
import initWasmModule from '/js/render_wasm.js';
|
||||
import {
|
||||
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill
|
||||
addShapeSolidFill, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||
getRandomFloat, addShape, setShapeChildren, setup
|
||||
} from './js/lib.js';
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
@@ -37,46 +37,43 @@
|
||||
const shapes = params.get("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);
|
||||
setup({
|
||||
instance: Module,
|
||||
canvas,
|
||||
shapes
|
||||
})
|
||||
|
||||
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(3);
|
||||
const x1 = getRandomInt(0, canvas.width);
|
||||
const y1 = getRandomInt(0, canvas.height);
|
||||
const width = getRandomInt(20, 100);
|
||||
const height = getRandomInt(20, 100);
|
||||
Module._set_shape_selrect(x1, y1, x1 + width, y1 + 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));
|
||||
addShapeSolidStrokeFill(argb2);
|
||||
for (let shape = 0; shape < shapes; shape++) {
|
||||
const color = getRandomColor()
|
||||
children.push(
|
||||
addShape({
|
||||
parent: "00000000-0000-0000-0000-000000000000",
|
||||
type: "rect", // rect
|
||||
selrect: {
|
||||
x: getRandomInt(0, canvas.width),
|
||||
y: getRandomInt(0, canvas.height),
|
||||
width: getRandomInt(20, 100),
|
||||
height: getRandomInt(20, 100),
|
||||
},
|
||||
fills: [{ type: "solid", color, opacity: getRandomFloat(0.1, 1.0) }],
|
||||
strokes: [{ width: 10, type: "solid", color, opacity: getRandomFloat(0.1, 1.0) }]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
useShape("00000000-0000-0000-0000-000000000000");
|
||||
setShapeChildren(children);
|
||||
addShape({
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
children: children
|
||||
})
|
||||
|
||||
performance.mark('render:begin');
|
||||
Module._render(Date.now());
|
||||
Module._render(performance.now(), true);
|
||||
performance.mark('render:end');
|
||||
const { duration } = performance.measure('render', 'render:begin', 'render:end');
|
||||
// alert(`render time: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -98,9 +98,15 @@
|
||||
|
||||
;; This should never be called from the outside.
|
||||
(defn- render
|
||||
[timestamp]
|
||||
(h/call wasm/internal-module "_render" timestamp)
|
||||
(set! wasm/internal-frame-id nil))
|
||||
([]
|
||||
(render (.now js/performance)))
|
||||
|
||||
([timestamp]
|
||||
(render timestamp false))
|
||||
|
||||
([timestamp full]
|
||||
(h/call wasm/internal-module "_render" timestamp full)
|
||||
(set! wasm/internal-frame-id nil)))
|
||||
|
||||
(def debounce-render (fns/debounce render 100))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
macro_rules! run_script {
|
||||
($s:expr) => {{
|
||||
extern "C" {
|
||||
pub fn emscripten_run_script(script: *const i8);
|
||||
fn emscripten_run_script(script: *const i8);
|
||||
}
|
||||
|
||||
match std::ffi::CString::new($s) {
|
||||
@@ -16,7 +16,7 @@ macro_rules! run_script {
|
||||
macro_rules! run_script_int {
|
||||
($s:expr) => {{
|
||||
extern "C" {
|
||||
pub fn emscripten_run_script_int(script: *const i8) -> i32;
|
||||
fn emscripten_run_script_int(script: *const i8) -> i32;
|
||||
}
|
||||
|
||||
match std::ffi::CString::new($s) {
|
||||
@@ -30,7 +30,7 @@ macro_rules! run_script_int {
|
||||
macro_rules! get_now {
|
||||
() => {{
|
||||
extern "C" {
|
||||
pub fn emscripten_get_now() -> f64;
|
||||
fn emscripten_get_now() -> f64;
|
||||
}
|
||||
unsafe { emscripten_get_now() }
|
||||
}};
|
||||
@@ -54,6 +54,54 @@ macro_rules! init_gl {
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! debugger {
|
||||
() => {{
|
||||
extern "C" {
|
||||
fn emscripten_debugger();
|
||||
}
|
||||
unsafe { emscripten_debugger() }
|
||||
}}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Log {
|
||||
Default = 1,
|
||||
Warn = 2,
|
||||
Error = 4,
|
||||
CStack = 8,
|
||||
JSStack = 16,
|
||||
Demangle = 32,
|
||||
NoPaths = 64,
|
||||
FuncParams = 128,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($flags:expr, $($arg:tt)*) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
extern "C" {
|
||||
fn emscripten_log(flags: u32, message: *const std::os::raw::c_char);
|
||||
}
|
||||
use std::ffi::CString;
|
||||
let msg = format!($($arg)*);
|
||||
let c_msg = CString::new(msg).unwrap();
|
||||
let flags = $flags as u32;
|
||||
unsafe {
|
||||
emscripten_log(flags, c_msg.as_ptr());
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use log;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use debugger;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use run_script;
|
||||
|
||||
@@ -65,3 +113,4 @@ pub use get_now;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use init_gl;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod emscripten;
|
||||
mod math;
|
||||
mod mem;
|
||||
@@ -119,10 +118,10 @@ pub extern "C" fn set_canvas_background(raw_color: u32) {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn render(_: i32) {
|
||||
pub extern "C" fn render(_: i32, full: bool) {
|
||||
with_state_mut!(state, {
|
||||
state
|
||||
.start_render_loop(performance::get_time())
|
||||
.start_render_loop(performance::get_time(), full)
|
||||
.expect("Error rendering");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,14 +12,15 @@ mod surfaces;
|
||||
mod text;
|
||||
mod ui;
|
||||
|
||||
use skia_safe::{self as skia, Matrix, Rect};
|
||||
use skia_safe::{self as skia, Color, Matrix, Rect};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use gpu_state::GpuState;
|
||||
use options::RenderOptions;
|
||||
use options::RenderStateOptions;
|
||||
use surfaces::{SurfaceId, Surfaces};
|
||||
|
||||
use crate::emscripten;
|
||||
use crate::performance;
|
||||
use crate::shapes::{Corners, Fill, Shape, SolidColor, StructureEntry, Type};
|
||||
use crate::state::ShapesPool;
|
||||
@@ -37,6 +38,104 @@ const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1;
|
||||
const MAX_BLOCKING_TIME_MS: i32 = 32;
|
||||
const NODE_BATCH_THRESHOLD: i32 = 10;
|
||||
|
||||
pub struct PendingNodes {
|
||||
pub list: Vec<NodeRenderState>,
|
||||
}
|
||||
|
||||
impl PendingNodes {
|
||||
pub fn new_empty() -> Self {
|
||||
Self { list: vec![] }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.list.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.list.len()
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, valid_ids: Vec<Uuid>) {
|
||||
self.list
|
||||
.extend(valid_ids.into_iter().map(|id| NodeRenderState {
|
||||
id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<NodeRenderState> {
|
||||
self.list.pop()
|
||||
}
|
||||
|
||||
pub fn prepare(&mut self, tree: &ShapesPool) {
|
||||
self.list.clear();
|
||||
if self.list.capacity() < tree.len() {
|
||||
self.list.reserve(tree.len() - self.list.capacity());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_root(&mut self) {
|
||||
emscripten::log!(emscripten::Log::Default, "add_root");
|
||||
self.list.push(NodeRenderState {
|
||||
id: Uuid::nil(),
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_child(
|
||||
&mut self,
|
||||
child_id: &Uuid,
|
||||
children_clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
|
||||
) {
|
||||
emscripten::log!(emscripten::Log::Default, "add_child");
|
||||
self.list.push(NodeRenderState {
|
||||
id: *child_id,
|
||||
visited_children: false,
|
||||
clip_bounds: children_clip_bounds,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_children_safeguard(&mut self, element: &Shape, mask: bool) {
|
||||
emscripten::log!(emscripten::Log::Default, "add_children_safeguard");
|
||||
// Set the node as visited_children before processing children
|
||||
self.list.push(NodeRenderState {
|
||||
id: element.id,
|
||||
visited_children: true,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_mask(&mut self, element: &Shape) {
|
||||
emscripten::log!(emscripten::Log::Default, "add_mask");
|
||||
self.list.push(NodeRenderState {
|
||||
id: element.id,
|
||||
visited_children: true,
|
||||
clip_bounds: None,
|
||||
visited_mask: true,
|
||||
mask: false,
|
||||
});
|
||||
if let Some(&mask_id) = element.mask_id() {
|
||||
self.list.push(NodeRenderState {
|
||||
id: mask_id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodeRenderState {
|
||||
pub id: Uuid,
|
||||
// We use this bool to keep that we've traversed all the children inside this node.
|
||||
@@ -151,7 +250,7 @@ impl FocusMode {
|
||||
|
||||
pub(crate) struct RenderState {
|
||||
gpu_state: GpuState,
|
||||
pub options: RenderOptions,
|
||||
pub options: RenderStateOptions,
|
||||
pub surfaces: Surfaces,
|
||||
pub fonts: FontStore,
|
||||
pub viewbox: Viewbox,
|
||||
@@ -163,8 +262,9 @@ pub(crate) struct RenderState {
|
||||
pub render_request_id: Option<i32>,
|
||||
// Indicates whether the rendering process has pending frames.
|
||||
pub render_in_progress: bool,
|
||||
pub render_is_full: bool,
|
||||
// Stack of nodes pending to be rendered.
|
||||
pending_nodes: Vec<NodeRenderState>,
|
||||
pending_nodes: PendingNodes,
|
||||
pub current_tile: Option<tiles::Tile>,
|
||||
pub sampling_options: skia::SamplingOptions,
|
||||
pub render_area: Rect,
|
||||
@@ -220,19 +320,22 @@ impl RenderState {
|
||||
let viewbox = Viewbox::new(width as f32, height as f32);
|
||||
let tiles = tiles::TileHashMap::new();
|
||||
|
||||
let context = gpu_state.context.clone();
|
||||
|
||||
RenderState {
|
||||
gpu_state: gpu_state.clone(),
|
||||
options: RenderOptions::default(),
|
||||
gpu_state,
|
||||
options: RenderStateOptions::default(),
|
||||
surfaces,
|
||||
fonts,
|
||||
viewbox,
|
||||
cached_viewbox: Viewbox::new(0., 0.),
|
||||
cached_target_snapshot: None,
|
||||
images: ImageStore::new(gpu_state.context.clone()),
|
||||
images: ImageStore::new(context),
|
||||
background_color: skia::Color::TRANSPARENT,
|
||||
render_request_id: None,
|
||||
render_in_progress: false,
|
||||
pending_nodes: vec![],
|
||||
render_is_full: false,
|
||||
pending_nodes: PendingNodes::new_empty(),
|
||||
current_tile: None,
|
||||
sampling_options,
|
||||
render_area: Rect::new_empty(),
|
||||
@@ -312,6 +415,11 @@ impl RenderState {
|
||||
self.surfaces.canvas(surface_id).restore();
|
||||
}
|
||||
|
||||
pub fn apply_full_render_to_final_canvas(&mut self) {
|
||||
emscripten::log!(emscripten::Log::Default, "apply_full_render_to_final_canvas");
|
||||
self.surfaces.draw_into(SurfaceId::Full, SurfaceId::Target, None);
|
||||
}
|
||||
|
||||
pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect) {
|
||||
let tile_rect = self.get_current_aligned_tile_bounds();
|
||||
self.surfaces.cache_current_tile_texture(
|
||||
@@ -336,6 +444,58 @@ impl RenderState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_drawing_to_full_render_canvas(&mut self, shape: Option<&Shape>) {
|
||||
performance::begin_measure!("apply_drawing_to_full_render_canvas");
|
||||
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::Full,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::Fills,
|
||||
SurfaceId::Full,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
|
||||
let mut render_overlay_below_strokes = false;
|
||||
if let Some(shape) = shape {
|
||||
render_overlay_below_strokes = shape.has_fills();
|
||||
}
|
||||
|
||||
if render_overlay_below_strokes {
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::InnerShadows,
|
||||
SurfaceId::Full,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
}
|
||||
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::Strokes,
|
||||
SurfaceId::Full,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
|
||||
if !render_overlay_below_strokes {
|
||||
self.surfaces.draw_into(
|
||||
SurfaceId::InnerShadows,
|
||||
SurfaceId::Full,
|
||||
Some(&skia::Paint::default()),
|
||||
);
|
||||
}
|
||||
let surface_ids = SurfaceId::Strokes as u32
|
||||
| SurfaceId::Fills as u32
|
||||
| SurfaceId::DropShadows as u32
|
||||
| SurfaceId::InnerShadows as u32;
|
||||
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
s.canvas().clear(skia::Color::TRANSPARENT);
|
||||
});
|
||||
performance::end_measure!("apply_drawing_to_full_render_canvas");
|
||||
}
|
||||
|
||||
pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>) {
|
||||
performance::begin_measure!("apply_drawing_to_render_canvas");
|
||||
|
||||
@@ -385,6 +545,8 @@ impl RenderState {
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
s.canvas().clear(skia::Color::TRANSPARENT);
|
||||
});
|
||||
|
||||
performance::end_measure!("apply_drawing_to_render_canvas");
|
||||
}
|
||||
|
||||
pub fn clear_focus_mode(&mut self) {
|
||||
@@ -401,6 +563,7 @@ impl RenderState {
|
||||
modifiers: Option<&Matrix>,
|
||||
scale_content: Option<&f32>,
|
||||
) {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape {}", shape.id);
|
||||
let shape = if let Some(scale_content) = scale_content {
|
||||
&shape.scale_content(*scale_content)
|
||||
} else {
|
||||
@@ -412,6 +575,7 @@ impl RenderState {
|
||||
| SurfaceId::DropShadows as u32
|
||||
| SurfaceId::InnerShadows as u32;
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape::save");
|
||||
s.canvas().save();
|
||||
});
|
||||
|
||||
@@ -432,8 +596,10 @@ impl RenderState {
|
||||
match &shape.shape_type {
|
||||
Type::SVGRaw(sr) => {
|
||||
if let Some(modifiers) = modifiers {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape::concat (modifiers)");
|
||||
self.surfaces.canvas(SurfaceId::Fills).concat(modifiers);
|
||||
}
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape::concat");
|
||||
self.surfaces.canvas(SurfaceId::Fills).concat(&matrix);
|
||||
if let Some(svg) = shape.svg.as_ref() {
|
||||
svg.render(self.surfaces.canvas(SurfaceId::Fills))
|
||||
@@ -458,6 +624,7 @@ impl RenderState {
|
||||
| SurfaceId::DropShadows as u32
|
||||
| SurfaceId::InnerShadows as u32;
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape::concat");
|
||||
s.canvas().concat(&matrix);
|
||||
});
|
||||
|
||||
@@ -517,6 +684,7 @@ impl RenderState {
|
||||
| SurfaceId::DropShadows as u32
|
||||
| SurfaceId::InnerShadows as u32;
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape::concat");
|
||||
s.canvas().concat(&matrix);
|
||||
});
|
||||
|
||||
@@ -557,19 +725,25 @@ impl RenderState {
|
||||
| SurfaceId::DropShadows as u32
|
||||
| SurfaceId::InnerShadows as u32;
|
||||
self.surfaces.apply_mut(surface_ids, |s| {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape::restore");
|
||||
s.canvas().restore();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_render_context(&mut self, tile: tiles::Tile) {
|
||||
self.current_tile = Some(tile);
|
||||
self.render_area = tiles::get_tile_rect(tile, self.get_scale());
|
||||
pub fn get_tiles_of(&mut self, id: Uuid) -> Option<&HashSet<tiles::Tile>> {
|
||||
self.tiles.get_tiles_of(id)
|
||||
}
|
||||
|
||||
pub fn update_render_context(&mut self, tile: &tiles::Tile) {
|
||||
self.current_tile = Some(*tile);
|
||||
self.render_area = tiles::get_tile_rect(*tile, self.get_scale());
|
||||
self.surfaces
|
||||
.update_render_context(self.render_area, self.get_scale());
|
||||
}
|
||||
|
||||
pub fn cancel_animation_frame(&mut self) {
|
||||
if self.render_in_progress {
|
||||
// self.render_in_progress = false;
|
||||
if let Some(frame_id) = self.render_request_id {
|
||||
wapi::cancel_animation_frame!(frame_id);
|
||||
}
|
||||
@@ -624,6 +798,7 @@ impl RenderState {
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
timestamp: i32,
|
||||
full: bool,
|
||||
) -> Result<(), String> {
|
||||
let scale = self.get_scale();
|
||||
self.tile_viewbox.update(self.viewbox, scale);
|
||||
@@ -658,14 +833,16 @@ impl RenderState {
|
||||
self.pending_tiles.update(&self.tile_viewbox);
|
||||
performance::end_measure!("tile_cache");
|
||||
|
||||
self.pending_nodes.clear();
|
||||
if self.pending_nodes.capacity() < tree.len() {
|
||||
self.pending_nodes
|
||||
.reserve(tree.len() - self.pending_nodes.capacity());
|
||||
}
|
||||
// reorder by distance to the center.
|
||||
self.pending_nodes.prepare(tree);
|
||||
|
||||
self.current_tile = None;
|
||||
|
||||
self.render_in_progress = true;
|
||||
self.render_is_full = full;
|
||||
if self.render_is_full {
|
||||
emscripten::log!(emscripten::Log::Default, "pending_nodes.add_root");
|
||||
self.pending_nodes.add_root();
|
||||
}
|
||||
self.apply_drawing_to_render_canvas(None);
|
||||
self.process_animation_frame(tree, modifiers, structure, scale_content, timestamp)?;
|
||||
performance::end_measure!("start_render_loop");
|
||||
@@ -682,14 +859,23 @@ impl RenderState {
|
||||
) -> Result<(), String> {
|
||||
performance::begin_measure!("process_animation_frame");
|
||||
if self.render_in_progress {
|
||||
self.render_shape_tree_partial(tree, modifiers, structure, scale_content, timestamp)?;
|
||||
if self.render_is_full {
|
||||
self.render_shape_tree_full(tree, modifiers, structure, scale_content, timestamp)?;
|
||||
} else {
|
||||
self.render_shape_tree_partial(
|
||||
tree,
|
||||
modifiers,
|
||||
structure,
|
||||
scale_content,
|
||||
timestamp,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.flush_and_submit();
|
||||
|
||||
if self.render_in_progress {
|
||||
self.cancel_animation_frame();
|
||||
self.render_request_id = Some(wapi::request_animation_frame!());
|
||||
} else {
|
||||
performance::end_measure!("render");
|
||||
}
|
||||
}
|
||||
performance::end_measure!("process_animation_frame");
|
||||
@@ -698,8 +884,23 @@ impl RenderState {
|
||||
|
||||
#[inline]
|
||||
pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool {
|
||||
iteration % NODE_BATCH_THRESHOLD == 0
|
||||
&& performance::get_time() - timestamp > MAX_BLOCKING_TIME_MS
|
||||
self.pending_nodes.is_empty() ||
|
||||
(iteration % NODE_BATCH_THRESHOLD == 0
|
||||
&& performance::get_time() - timestamp > MAX_BLOCKING_TIME_MS)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn render_current_tile_to_final_canvas(&mut self, is_empty: bool) {
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
if !is_empty {
|
||||
self.apply_render_to_final_canvas(tile_rect);
|
||||
} else {
|
||||
self.surfaces.apply_mut(SurfaceId::Target as u32, |s| {
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_color(self.background_color);
|
||||
s.canvas().draw_rect(tile_rect, &paint);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -774,22 +975,7 @@ impl RenderState {
|
||||
// the blend mode 'destination-in') the content
|
||||
// of the group and the mask.
|
||||
if group.masked {
|
||||
self.pending_nodes.push(NodeRenderState {
|
||||
id: element.id,
|
||||
visited_children: true,
|
||||
clip_bounds: None,
|
||||
visited_mask: true,
|
||||
mask: false,
|
||||
});
|
||||
if let Some(&mask_id) = element.mask_id() {
|
||||
self.pending_nodes.push(NodeRenderState {
|
||||
id: mask_id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: true,
|
||||
});
|
||||
}
|
||||
self.pending_nodes.add_mask(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -832,43 +1018,22 @@ impl RenderState {
|
||||
self.focus_mode.exit(&element.id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_tile_bounds(&mut self, tile: tiles::Tile) -> Rect {
|
||||
tiles::get_tile_bounds(self.viewbox, tile, self.get_scale())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_current_tile_bounds(&mut self) -> Rect {
|
||||
let tiles::Tile(tile_x, tile_y) = self.current_tile.unwrap();
|
||||
let scale = self.get_scale();
|
||||
let offset_x = self.viewbox.area.left * scale;
|
||||
let offset_y = self.viewbox.area.top * scale;
|
||||
Rect::from_xywh(
|
||||
(tile_x as f32 * tiles::TILE_SIZE) - offset_x,
|
||||
(tile_y as f32 * tiles::TILE_SIZE) - offset_y,
|
||||
tiles::TILE_SIZE,
|
||||
tiles::TILE_SIZE,
|
||||
)
|
||||
self.get_tile_bounds(self.current_tile.unwrap())
|
||||
}
|
||||
|
||||
// Returns the bounds of the current tile relative to the viewbox,
|
||||
// aligned to the nearest tile grid origin.
|
||||
//
|
||||
// Unlike `get_current_tile_bounds`, which calculates bounds using the exact
|
||||
// scaled offset of the viewbox, this method snaps the origin to the nearest
|
||||
// lower multiple of `TILE_SIZE`. This ensures the tile bounds are aligned
|
||||
// with the global tile grid, which is useful for rendering tiles in a
|
||||
/// consistent and predictable layout.
|
||||
#[inline]
|
||||
pub fn get_current_aligned_tile_bounds(&mut self) -> Rect {
|
||||
let tiles::Tile(tile_x, tile_y) = self.current_tile.unwrap();
|
||||
let scale = self.get_scale();
|
||||
let start_tile_x =
|
||||
(self.viewbox.area.left * scale / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
let start_tile_y =
|
||||
(self.viewbox.area.top * scale / tiles::TILE_SIZE).floor() * tiles::TILE_SIZE;
|
||||
Rect::from_xywh(
|
||||
(tile_x as f32 * tiles::TILE_SIZE) - start_tile_x,
|
||||
(tile_y as f32 * tiles::TILE_SIZE) - start_tile_y,
|
||||
tiles::TILE_SIZE,
|
||||
tiles::TILE_SIZE,
|
||||
)
|
||||
tiles::get_tile_aligned_bounds(self.viewbox, self.current_tile.unwrap(), self.get_scale())
|
||||
}
|
||||
|
||||
pub fn render_shape_tree_partial_uncached(
|
||||
pub fn render_shape_tree_full(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
@@ -876,9 +1041,11 @@ impl RenderState {
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
timestamp: i32,
|
||||
) -> Result<(bool, bool), String> {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape_tree_full:begin");
|
||||
let mut iteration = 0;
|
||||
let mut is_empty = true;
|
||||
while let Some(node_render_state) = self.pending_nodes.pop() {
|
||||
let scale = self.get_scale();
|
||||
while let Some(node_render_state) = self.pending_nodes.next() {
|
||||
let NodeRenderState {
|
||||
id: node_id,
|
||||
visited_children,
|
||||
@@ -896,7 +1063,129 @@ impl RenderState {
|
||||
// If the shape is not in the tile set, then we update
|
||||
// it.
|
||||
if self.tiles.get_tiles_of(node_id).is_none() {
|
||||
self.update_tile_for(element);
|
||||
self.update_tiles_for(element);
|
||||
}
|
||||
|
||||
if visited_children {
|
||||
self.render_shape_exit(
|
||||
element,
|
||||
visited_mask,
|
||||
modifiers.get(&node_id),
|
||||
scale_content.get(&element.id),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if !node_render_state.is_root() {
|
||||
// No necesitamos esto porque en el primer render es imposible
|
||||
// que se estén transformando elementos.
|
||||
/*
|
||||
let mut transformed_element: Cow<Shape> = Cow::Borrowed(element);
|
||||
|
||||
if let Some(modifier) = modifiers.get(&node_id) {
|
||||
transformed_element.to_mut().apply_transform(modifier);
|
||||
}
|
||||
|
||||
let is_visible = transformed_element.extrect().intersects(self.render_area)
|
||||
&& !transformed_element.hidden
|
||||
&& !transformed_element.visually_insignificant(scale);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_debug_shape(self, &transformed_element, is_visible);
|
||||
}
|
||||
*/
|
||||
|
||||
let is_visible = !element.hidden
|
||||
&& !element.visually_insignificant(scale);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_debug_shape(self, element, is_visible);
|
||||
}
|
||||
|
||||
if !is_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
self.render_shape_enter(element, mask);
|
||||
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
||||
self.render_shape(
|
||||
element,
|
||||
modifiers.get(&element.id),
|
||||
scale_content.get(&element.id),
|
||||
);
|
||||
} else if visited_children {
|
||||
// NOTA: Esto no debería usarse porque esta función utiliza el CURRENT.
|
||||
self.apply_drawing_to_full_render_canvas(Some(element));
|
||||
}
|
||||
|
||||
// Set the node as visited_children before processing children
|
||||
self.pending_nodes.add_children_safeguard(element, mask);
|
||||
|
||||
if element.is_recursive() {
|
||||
let children_clip_bounds =
|
||||
node_render_state.get_children_clip_bounds(element, modifiers.get(&element.id));
|
||||
|
||||
let mut children_ids =
|
||||
element.modified_children_ids(structure.get(&element.id), false);
|
||||
|
||||
// Z-index ordering on Layouts
|
||||
if element.has_layout() {
|
||||
children_ids.sort_by(|id1, id2| {
|
||||
let z1 = tree.get(id1).map_or_else(|| 0, |s| s.z_index());
|
||||
let z2 = tree.get(id2).map_or_else(|| 0, |s| s.z_index());
|
||||
z1.cmp(&z2)
|
||||
});
|
||||
}
|
||||
|
||||
for child_id in children_ids.iter() {
|
||||
self.pending_nodes.add_child(child_id, children_clip_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
// We try to avoid doing too many calls to get_time
|
||||
if self.should_stop_rendering(iteration, timestamp) {
|
||||
return Ok((is_empty, true));
|
||||
}
|
||||
iteration += 1;
|
||||
}
|
||||
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape_tree_full:end");
|
||||
self.render_in_progress = false;
|
||||
self.apply_full_render_to_final_canvas();
|
||||
Ok((false, true))
|
||||
}
|
||||
|
||||
pub fn render_shape_tree_partial_uncached(
|
||||
&mut self,
|
||||
tree: &ShapesPool,
|
||||
modifiers: &HashMap<Uuid, Matrix>,
|
||||
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
timestamp: i32,
|
||||
) -> Result<(bool, bool), String> {
|
||||
let mut iteration = 0;
|
||||
let mut is_empty = true;
|
||||
let scale = self.get_scale();
|
||||
while let Some(node_render_state) = self.pending_nodes.next() {
|
||||
let NodeRenderState {
|
||||
id: node_id,
|
||||
visited_children,
|
||||
clip_bounds: _,
|
||||
visited_mask,
|
||||
mask,
|
||||
} = node_render_state;
|
||||
|
||||
is_empty = false;
|
||||
let element = tree.get(&node_id).ok_or(
|
||||
"Error: Element with root_id {node_render_state.id} not found in the tree."
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
// If the shape is not in the tile set, then we update
|
||||
// it.
|
||||
if self.tiles.get_tiles_of(node_id).is_none() {
|
||||
self.update_tiles_for(element);
|
||||
}
|
||||
|
||||
if visited_children {
|
||||
@@ -918,7 +1207,7 @@ impl RenderState {
|
||||
|
||||
let is_visible = transformed_element.extrect().intersects(self.render_area)
|
||||
&& !transformed_element.hidden
|
||||
&& !transformed_element.visually_insignificant(self.get_scale());
|
||||
&& !transformed_element.visually_insignificant(scale);
|
||||
|
||||
if self.options.is_debug_visible() {
|
||||
debug::render_debug_shape(self, &transformed_element, is_visible);
|
||||
@@ -941,13 +1230,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
// Set the node as visited_children before processing children
|
||||
self.pending_nodes.push(NodeRenderState {
|
||||
id: node_id,
|
||||
visited_children: true,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask,
|
||||
});
|
||||
self.pending_nodes.add_children_safeguard(element, mask);
|
||||
|
||||
if element.is_recursive() {
|
||||
let children_clip_bounds =
|
||||
@@ -966,13 +1249,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
for child_id in children_ids.iter() {
|
||||
self.pending_nodes.push(NodeRenderState {
|
||||
id: *child_id,
|
||||
visited_children: false,
|
||||
clip_bounds: children_clip_bounds,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
});
|
||||
self.pending_nodes.add_child(child_id, children_clip_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,10 +1270,12 @@ impl RenderState {
|
||||
scale_content: &HashMap<Uuid, f32>,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape_tree_partial:begin");
|
||||
let mut should_stop = false;
|
||||
while !should_stop {
|
||||
if let Some(current_tile) = self.current_tile {
|
||||
if self.surfaces.has_cached_tile_surface(current_tile) {
|
||||
emscripten::log!(emscripten::Log::Default, "cached");
|
||||
performance::begin_measure!("render_shape_tree::cached");
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
self.surfaces.draw_cached_tile_surface(
|
||||
@@ -1015,6 +1294,7 @@ impl RenderState {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
emscripten::log!(emscripten::Log::Default, "uncached");
|
||||
performance::begin_measure!("render_shape_tree::uncached");
|
||||
let (is_empty, early_return) = self.render_shape_tree_partial_uncached(
|
||||
tree,
|
||||
@@ -1027,16 +1307,7 @@ impl RenderState {
|
||||
return Ok(());
|
||||
}
|
||||
performance::end_measure!("render_shape_tree::uncached");
|
||||
let tile_rect = self.get_current_tile_bounds();
|
||||
if !is_empty {
|
||||
self.apply_render_to_final_canvas(tile_rect);
|
||||
} else {
|
||||
self.surfaces.apply_mut(SurfaceId::Target as u32, |s| {
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_color(self.background_color);
|
||||
s.canvas().draw_rect(tile_rect, &paint);
|
||||
});
|
||||
}
|
||||
self.render_current_tile_to_final_canvas(is_empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1053,7 +1324,7 @@ impl RenderState {
|
||||
// If we finish processing every node rendering is complete
|
||||
// let's check if there are more pending nodes
|
||||
if let Some(next_tile) = self.pending_tiles.pop() {
|
||||
self.update_render_context(next_tile);
|
||||
self.update_render_context(&next_tile);
|
||||
|
||||
if !self.surfaces.has_cached_tile_surface(next_tile) {
|
||||
if let Some(ids) = self.tiles.get_shapes_at(next_tile) {
|
||||
@@ -1062,19 +1333,9 @@ impl RenderState {
|
||||
.iter()
|
||||
.filter_map(|id| root_ids.get(id).map(|_| *id))
|
||||
.collect();
|
||||
|
||||
// These shapes for the tile should be ordered as they are in the parent node
|
||||
valid_ids.sort_by_key(|id| root_ids.get_index_of(id));
|
||||
|
||||
self.pending_nodes.extend(valid_ids.into_iter().map(|id| {
|
||||
NodeRenderState {
|
||||
id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
}
|
||||
}));
|
||||
self.pending_nodes.extend(valid_ids);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1093,7 +1354,7 @@ impl RenderState {
|
||||
|
||||
ui::render(self, tree, modifiers, structure);
|
||||
debug::render_wasm_label(self);
|
||||
|
||||
emscripten::log!(emscripten::Log::Default, "render_shape_tree_partial:end");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1102,7 +1363,7 @@ impl RenderState {
|
||||
tiles::get_tiles_for_rect(shape.extrect(), tile_size)
|
||||
}
|
||||
|
||||
pub fn update_tile_for(&mut self, shape: &Shape) {
|
||||
pub fn update_tiles_for(&mut self, shape: &Shape) {
|
||||
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape);
|
||||
let new_tiles: HashSet<tiles::Tile> = (rsx..=rex)
|
||||
.flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile(x, y)))
|
||||
@@ -1144,7 +1405,7 @@ impl RenderState {
|
||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||
shape.to_mut().apply_transform(modifier);
|
||||
}
|
||||
self.update_tile_for(&shape);
|
||||
self.update_tiles_for(&shape);
|
||||
} else {
|
||||
// We only need to rebuild tiles from the first level.
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||
@@ -1174,7 +1435,7 @@ impl RenderState {
|
||||
if let Some(modifier) = modifiers.get(&shape_id) {
|
||||
shape.to_mut().apply_transform(modifier);
|
||||
}
|
||||
self.update_tile_for(&shape);
|
||||
self.update_tiles_for(&shape);
|
||||
}
|
||||
|
||||
let children = shape.modified_children_ids(structure.get(&shape.id), false);
|
||||
@@ -1191,7 +1452,7 @@ impl RenderState {
|
||||
if let Some(shape) = tree.get(uuid) {
|
||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||
shape.to_mut().apply_transform(matrix);
|
||||
self.update_tile_for(&shape);
|
||||
self.update_tiles_for(&shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::options;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Default)]
|
||||
pub struct RenderOptions {
|
||||
pub struct RenderStateOptions {
|
||||
pub flags: u32,
|
||||
pub dpr: Option<f32>,
|
||||
}
|
||||
|
||||
impl RenderOptions {
|
||||
impl RenderStateOptions {
|
||||
pub fn is_debug_visible(&self) -> bool {
|
||||
self.flags & options::DEBUG_VISIBLE == options::DEBUG_VISIBLE
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub enum SurfaceId {
|
||||
InnerShadows = 0b0_0100_0000,
|
||||
UI = 0b0_1000_0000,
|
||||
Debug = 0b1_0000_0000,
|
||||
Full = 0b10_0000_0000,
|
||||
}
|
||||
|
||||
pub struct Surfaces {
|
||||
@@ -46,6 +47,8 @@ pub struct Surfaces {
|
||||
ui: skia::Surface,
|
||||
// for drawing debug info.
|
||||
debug: skia::Surface,
|
||||
// for slicing a full render.
|
||||
full: skia::Surface,
|
||||
// for drawing tiles.
|
||||
tiles: TileTextureCache,
|
||||
sampling_options: skia::SamplingOptions,
|
||||
@@ -80,6 +83,7 @@ impl Surfaces {
|
||||
|
||||
let ui = gpu_state.create_surface_with_dimensions("ui".to_string(), width, height);
|
||||
let debug = gpu_state.create_surface_with_dimensions("debug".to_string(), width, height);
|
||||
let full = gpu_state.create_surface_with_dimensions("full".to_string(), width, height);
|
||||
|
||||
let tiles = TileTextureCache::new();
|
||||
Surfaces {
|
||||
@@ -92,6 +96,7 @@ impl Surfaces {
|
||||
shape_strokes,
|
||||
ui,
|
||||
debug,
|
||||
full,
|
||||
tiles,
|
||||
sampling_options,
|
||||
margins,
|
||||
@@ -172,6 +177,9 @@ impl Surfaces {
|
||||
if ids & SurfaceId::Debug as u32 != 0 {
|
||||
f(self.get_mut(SurfaceId::Debug));
|
||||
}
|
||||
if ids & SurfaceId::Full as u32 != 0 {
|
||||
f(self.get_mut(SurfaceId::Full));
|
||||
}
|
||||
performance::begin_measure!("apply_mut::flags");
|
||||
}
|
||||
|
||||
@@ -205,6 +213,7 @@ impl Surfaces {
|
||||
SurfaceId::Strokes => &mut self.shape_strokes,
|
||||
SurfaceId::Debug => &mut self.debug,
|
||||
SurfaceId::UI => &mut self.ui,
|
||||
SurfaceId::Full => &mut self.full,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +222,7 @@ impl Surfaces {
|
||||
self.target = target;
|
||||
self.debug = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.ui = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
self.full = self.target.new_surface_with_dimensions(dim).unwrap();
|
||||
// The rest are tile size surfaces
|
||||
}
|
||||
|
||||
|
||||
@@ -816,11 +816,15 @@ impl Shape {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.id.is_nil()
|
||||
}
|
||||
|
||||
pub fn is_recursive(&self) -> bool {
|
||||
matches!(
|
||||
self.shape_type,
|
||||
Type::Frame(_) | Type::Group(_) | Type::Bool(_)
|
||||
)
|
||||
) || self.id.is_nil()
|
||||
}
|
||||
|
||||
pub fn add_shadow(&mut self, shadow: Shadow) {
|
||||
|
||||
@@ -55,13 +55,14 @@ impl State {
|
||||
.render_from_cache(&self.shapes, &self.modifiers, &self.structure);
|
||||
}
|
||||
|
||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
||||
pub fn start_render_loop(&mut self, timestamp: i32, full: bool) -> Result<(), String> {
|
||||
self.render_state.start_render_loop(
|
||||
&self.shapes,
|
||||
&self.modifiers,
|
||||
&self.structure,
|
||||
&self.scale_content,
|
||||
timestamp,
|
||||
full,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -133,13 +134,13 @@ impl State {
|
||||
|
||||
// We don't need to update the tile for the root shape.
|
||||
if !shape.id.is_nil() {
|
||||
self.render_state.update_tile_for(&shape);
|
||||
self.render_state.update_tiles_for(&shape);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_tile_for_shape(&mut self, shape_id: Uuid) {
|
||||
if let Some(shape) = self.shapes.get(&shape_id) {
|
||||
self.render_state.update_tile_for(shape);
|
||||
self.render_state.update_tiles_for(shape);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ impl State {
|
||||
panic!("Invalid current shape")
|
||||
};
|
||||
if !shape.id.is_nil() {
|
||||
self.render_state.update_tile_for(&shape.clone());
|
||||
self.render_state.update_tiles_for(&shape.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,38 @@ pub fn get_tile_rect(tile: Tile, scale: f32) -> skia::Rect {
|
||||
skia::Rect::from_xywh(tx, ty, ts, ts)
|
||||
}
|
||||
|
||||
pub fn get_tile_bounds(viewbox: Viewbox, Tile(tile_x, tile_y): Tile, scale: f32) -> skia::Rect {
|
||||
let offset_x = viewbox.area.left * scale;
|
||||
let offset_y = viewbox.area.top * scale;
|
||||
skia::Rect::from_xywh(
|
||||
(tile_x as f32 * TILE_SIZE) - offset_x,
|
||||
(tile_y as f32 * TILE_SIZE) - offset_y,
|
||||
TILE_SIZE,
|
||||
TILE_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
// Returns the bounds of the current tile relative to the viewbox,
|
||||
// aligned to the nearest tile grid origin.
|
||||
//
|
||||
// Unlike `get_current_tile_bounds`, which calculates bounds using the exact
|
||||
// scaled offset of the viewbox, this method snaps the origin to the nearest
|
||||
// lower multiple of `TILE_SIZE`. This ensures the tile bounds are aligned
|
||||
// with the global tile grid, which is useful for rendering tiles in a
|
||||
// consistent and predictable layout.
|
||||
pub fn get_tile_aligned_bounds(viewbox: Viewbox, Tile(tile_x, tile_y): Tile, scale: f32) -> skia::Rect {
|
||||
let start_tile_x =
|
||||
(viewbox.area.left * scale / TILE_SIZE).floor() * TILE_SIZE;
|
||||
let start_tile_y =
|
||||
(viewbox.area.top * scale / TILE_SIZE).floor() * TILE_SIZE;
|
||||
skia::Rect::from_xywh(
|
||||
(tile_x as f32 * TILE_SIZE) - start_tile_x,
|
||||
(tile_y as f32 * TILE_SIZE) - start_tile_y,
|
||||
TILE_SIZE,
|
||||
TILE_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
// This structure is usseful to keep all the shape uuids by shape id.
|
||||
pub struct TileHashMap {
|
||||
grid: HashMap<Tile, IndexSet<Uuid>>,
|
||||
@@ -176,6 +208,10 @@ impl PendingTiles {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.list.len()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, tile_viewbox: &TileViewbox) {
|
||||
self.list.clear();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user