perf(canvas): cache vector stroke paths

This commit is contained in:
Danila Poyarkov 2026-05-17 02:21:53 +03:00
parent 197e19b6f4
commit 0b3b620b54
3 changed files with 36 additions and 16 deletions

View file

@ -72,6 +72,8 @@ export class SkiaRenderer {
fontsLoaded = false
imageCache = new Map<string, CKImage>()
vectorPathCache = new Map<string, Path[]>()
vectorStrokePathCache = new Map<string, Path[]>()
vectorStrokeOutlineCache = new Map<string, Path[]>()
fillGeometryCache = new Map<string, Path[]>()
strokeGeometryCache = new Map<string, Path[]>()
scenePicture: SkPicture | null = null
@ -485,10 +487,16 @@ export class SkiaRenderer {
}
invalidateVectorPath(nodeId: string): void {
const old = this.vectorPathCache.get(nodeId)
if (old) {
for (const cache of [this.vectorPathCache, this.vectorStrokePathCache]) {
const old = cache.get(nodeId)
if (!old) continue
for (const p of old) p.delete()
this.vectorPathCache.delete(nodeId)
cache.delete(nodeId)
}
for (const [key, paths] of this.vectorStrokeOutlineCache) {
if (!key.startsWith(`${nodeId}|`)) continue
for (const p of paths) p.delete()
this.vectorStrokeOutlineCache.delete(key)
}
for (const cache of [this.fillGeometryCache, this.strokeGeometryCache]) {
const oldGeom = cache.get(nodeId)

View file

@ -7,7 +7,13 @@ export function destroyRenderer(r: SkiaRenderer): void {
for (const img of r.imageCache.values()) img.delete()
r.imageCache.clear()
for (const cache of [r.vectorPathCache, r.fillGeometryCache, r.strokeGeometryCache]) {
for (const cache of [
r.vectorPathCache,
r.vectorStrokePathCache,
r.vectorStrokeOutlineCache,
r.fillGeometryCache,
r.strokeGeometryCache
]) {
for (const paths of cache.values()) {
for (const p of paths) p.delete()
}

View file

@ -322,6 +322,8 @@ function drawVectorStrokeGeometry(
function vectorStrokePaths(r: SkiaRenderer, node: SceneNode): Path[] | null {
if (!node.vectorNetwork) return null
const cached = r.vectorStrokePathCache.get(node.id)
if (cached) return cached
const paths: Path[] = []
for (const segment of node.vectorNetwork.segments) {
@ -350,7 +352,9 @@ function vectorStrokePaths(r: SkiaRenderer, node: SceneNode): Path[] | null {
paths.push(path)
}
return paths.length > 0 ? paths : null
if (paths.length === 0) return null
r.vectorStrokePathCache.set(node.id, paths)
return paths
}
function drawVectorPathStrokes(
@ -358,7 +362,8 @@ function drawVectorPathStrokes(
canvas: Canvas,
vectorPaths: Path[],
stroke: SceneNode['strokes'][0],
sc: Color
sc: Color,
outlineCacheKey?: string
): void {
const dash = stroke.dashPattern
if (dash && dash.length > 0) {
@ -384,13 +389,17 @@ function drawVectorPathStrokes(
r.fillPaint.setColor(r.ck.Color4f(sc.r, sc.g, sc.b, sc.a))
r.fillPaint.setAlphaf(stroke.opacity)
r.fillPaint.setShader(null)
let outlines = outlineCacheKey ? r.vectorStrokeOutlineCache.get(outlineCacheKey) : undefined
if (!outlines) {
outlines = []
for (const vp of vectorPaths) {
const outline = vp.copy().stroke(strokeOpts)
if (outline) {
canvas.drawPath(outline, r.fillPaint)
outline.delete()
if (outline) outlines.push(outline)
}
if (outlineCacheKey) r.vectorStrokeOutlineCache.set(outlineCacheKey, outlines)
}
for (const outline of outlines) canvas.drawPath(outline, r.fillPaint)
}
function drawRegularStroke(
@ -438,7 +447,8 @@ function drawNodeStroke(
vectorStroke: Path[] | null
): void {
if (vectorStroke && stroke.align === 'CENTER' && node.cornerRadius === 0) {
drawVectorPathStrokes(r, canvas, vectorStroke, stroke, sc)
const outlineKey = `${node.id}|${stroke.weight}|${stroke.cap ?? 'NONE'}|${stroke.join ?? 'MITER'}`
drawVectorPathStrokes(r, canvas, vectorStroke, stroke, sc, outlineKey)
return
}
if (!sg) {
@ -498,10 +508,6 @@ export function renderShapeUncached(
}
drawNodeStroke(r, canvas, node, rect, hasRadius, stroke, color, sg, vectorPaths, vectorStroke)
})
if (vectorStroke) {
for (const path of vectorStroke) path.delete()
}
r.renderEffects(canvas, node, rect, hasRadius, 'front', shadowChild)
}