🐛 Fix frame clipping

This commit is contained in:
Alejandro Alonso 2025-07-03 07:50:50 +02:00
parent a44c70ef69
commit 51bb6583d2
4 changed files with 157 additions and 52 deletions

View file

@ -0,0 +1,90 @@
<!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, addShapeSolidStrokeFill,
set_parent
} from './js/lib.js';
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
initWasmModule().then(Module => {
init(Module);
assignCanvas(canvas);
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
Module._set_view(1, 0, 0);
Module._init_shapes_pool(10);
setupInteraction(canvas);
const rectUuid1 = crypto.randomUUID();
const rectUuid2 = crypto.randomUUID();
const frameUuid = crypto.randomUUID();
// Create Rect 1
useShape(rectUuid1);
set_parent(frameUuid);
Module._set_shape_type(3);
Module._set_shape_selrect(100, 100, 300, 300);
Module._set_shape_blur(1, 100, 40);
addShapeSolidFill(hexToU32ARGB("#003DF7", 1));
// Create Rect 2
useShape(rectUuid2);
set_parent(frameUuid);
Module._set_shape_type(3);
Module._set_shape_selrect(200, 200, 400, 400);
addShapeSolidFill(1870806498)
// Create Frame
useShape(frameUuid);
Module._set_parent(0, 0, 0, 0);
Module._set_shape_type(0);
Module._set_shape_selrect(200, 200, 450, 450);
Module._set_shape_corners(50, 50, 50, 50);
addShapeSolidFill(hexToU32ARGB("#ee0d32", 1))
Module._add_shape_center_stroke(25, 0, 0, 0);
addShapeSolidStrokeFill(hexToU32ARGB("#000000", 1));
Module._set_shape_blur(1, 100, 4);
Module._add_shape_shadow(hexToU32ARGB("#000000", .2), 4, 40, 80, 80, 2, false);
setShapeChildren([rectUuid1]);
useShape("00000000-0000-0000-0000-000000000000");
setShapeChildren([frameUuid]);
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>

View file

@ -12,7 +12,7 @@ mod surfaces;
mod text;
mod ui;
use skia_safe::{self as skia, Matrix, RRect, Rect};
use skia_safe::{self as skia, Matrix, Rect};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
@ -21,7 +21,7 @@ use options::RenderOptions;
use surfaces::{SurfaceId, Surfaces};
use crate::performance;
use crate::shapes::{Corners, Fill, Shape, StructureEntry, Type};
use crate::shapes::{Corners, Fill, Shape, SolidColor, StructureEntry, Type};
use crate::tiles::{self, PendingTiles, TileRect};
use crate::uuid::Uuid;
use crate::view::Viewbox;
@ -317,8 +317,11 @@ impl RenderState {
&tile_rect,
);
self.surfaces
.draw_cached_tile_surface(self.current_tile.unwrap(), rect);
self.surfaces.draw_cached_tile_surface(
self.current_tile.unwrap(),
rect,
self.background_color,
);
if self.options.is_debug_visible() {
debug::render_workspace_current_tile(
@ -394,7 +397,6 @@ impl RenderState {
shape: &Shape,
modifiers: Option<&Matrix>,
scale_content: Option<&f32>,
clip_bounds: Option<(Rect, Option<Corners>, Matrix)>,
) {
let shape = if let Some(scale_content) = scale_content {
&shape.scale_content(*scale_content)
@ -412,43 +414,6 @@ impl RenderState {
let antialias = shape.should_use_antialias(self.get_scale());
// set clipping
if let Some((bounds, corners, transform)) = clip_bounds {
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().concat(&transform);
});
if let Some(corners) = corners {
let rrect = RRect::new_rect_radii(bounds, &corners);
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas()
.clip_rrect(rrect, skia::ClipOp::Intersect, antialias);
});
} else {
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas()
.clip_rect(bounds, skia::ClipOp::Intersect, antialias);
});
}
// This renders a red line around clipped
// shapes (frames).
if self.options.is_debug_visible() {
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_color(skia::Color::from_argb(255, 255, 0, 0));
paint.set_stroke_width(4.);
self.surfaces
.canvas(SurfaceId::Fills)
.draw_rect(bounds, &paint);
}
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas()
.concat(&transform.invert().unwrap_or(Matrix::default()));
});
}
// We don't want to change the value in the global state
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
@ -566,7 +531,6 @@ impl RenderState {
shadows::render_fill_drop_shadows(self, &shape, antialias);
}
};
self.apply_drawing_to_render_canvas(Some(&shape));
let surface_ids = SurfaceId::Strokes as u32
| SurfaceId::Fills as u32
@ -765,7 +729,13 @@ impl RenderState {
}
#[inline]
pub fn render_shape_exit(&mut self, element: &Shape, visited_mask: bool) {
pub fn render_shape_exit(
&mut self,
element: &Shape,
visited_mask: bool,
modifiers: Option<&Matrix>,
scale_content: Option<&f32>,
) {
if visited_mask {
// Because masked groups needs two rendering passes (first drawing
// the content and then drawing the mask), we need to do an
@ -806,8 +776,39 @@ impl RenderState {
if let Type::Group(_) = element.shape_type {
self.nested_fills.pop();
}
self.surfaces.canvas(SurfaceId::Current).restore();
// Detect clipping and apply it properly
if let Type::Frame(_) = &element.shape_type {
if element.clip() {
let mut layer_paint = skia::Paint::default();
layer_paint.set_blend_mode(skia::BlendMode::DstIn);
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&layer_paint);
self.surfaces
.canvas(SurfaceId::Current)
.save_layer(&layer_rec);
// We clip only the fills content
let mut element_fills: Cow<Shape> = Cow::Borrowed(element);
element_fills.to_mut().clear_strokes();
element_fills.to_mut().clear_shadows();
// We use the white color as the mask selection one
element_fills
.to_mut()
.set_fills([Fill::Solid(SolidColor(skia::Color::WHITE))].to_vec());
self.render_shape(&element_fills, modifiers, scale_content);
self.surfaces.canvas(SurfaceId::Current).restore();
// Now we paint the strokes - in clipped content strokes are drawn over the contained elements
let mut element_strokes: Cow<Shape> = Cow::Borrowed(element);
element_strokes.to_mut().clear_fills();
element_strokes.to_mut().clear_shadows();
self.render_shape(&element_strokes, modifiers, scale_content);
// TODO: drop shadows. With thos approach actually drop shadows for frames with clipped content are lost.
}
}
self.surfaces.canvas(SurfaceId::Current).restore();
self.focus_mode.exit(&element.id);
}
@ -861,7 +862,7 @@ impl RenderState {
let NodeRenderState {
id: node_id,
visited_children,
clip_bounds,
clip_bounds: _,
visited_mask,
mask,
} = node_render_state;
@ -879,7 +880,12 @@ impl RenderState {
}
if visited_children {
self.render_shape_exit(element, visited_mask);
self.render_shape_exit(
element,
visited_mask,
modifiers.get(&node_id),
scale_content.get(&element.id),
);
continue;
}
@ -909,7 +915,6 @@ impl RenderState {
element,
modifiers.get(&element.id),
scale_content.get(&element.id),
clip_bounds,
);
} else if visited_children {
self.apply_drawing_to_render_canvas(Some(element));
@ -974,8 +979,11 @@ impl RenderState {
if self.surfaces.has_cached_tile_surface(current_tile) {
performance::begin_measure!("render_shape_tree::cached");
let tile_rect = self.get_current_tile_bounds();
self.surfaces
.draw_cached_tile_surface(current_tile, tile_rect);
self.surfaces.draw_cached_tile_surface(
current_tile,
tile_rect,
self.background_color,
);
performance::end_measure!("render_shape_tree::cached");
if self.options.is_debug_visible() {
@ -1012,6 +1020,7 @@ impl RenderState {
}
}
// println!("clear current {:?}", self.current_tile);
self.surfaces
.canvas(SurfaceId::Current)
.clear(self.background_color);

View file

@ -307,8 +307,14 @@ impl Surfaces {
self.tiles.remove(tile)
}
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect) {
pub fn draw_cached_tile_surface(&mut self, tile: Tile, rect: skia::Rect, color: skia::Color) {
let image = self.tiles.get(tile).unwrap();
let mut paint = skia::Paint::default();
paint.set_color(color);
self.target.canvas().draw_rect(rect, &paint);
self.target
.canvas()
.draw_image_rect(&image, None, rect, &skia::Paint::default());

View file

@ -4,7 +4,7 @@ use indexmap::IndexSet;
use skia_safe as skia;
use std::collections::{HashMap, HashSet};
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub struct Tile(pub i32, pub i32);
#[derive(PartialEq, Eq, Hash, Clone, Copy)]