Cache vector paths and pen paints across frames
This commit is contained in:
parent
97f4727e26
commit
80beba3348
|
|
@ -4,8 +4,10 @@
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
- Cache ruler paints instead of allocating per frame
|
- Cache vector network paths — avoid rebuilding WASM paths every frame
|
||||||
|
- Cache ruler and pen overlay paints — eliminate 10 WASM Paint allocations per frame
|
||||||
- Only enable `preserveDrawingBuffer` in test mode
|
- Only enable `preserveDrawingBuffer` in test mode
|
||||||
|
- Hoist URL param parsing out of render loop
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,12 +132,17 @@ export class SkiaRenderer {
|
||||||
private fontProvider: TypefaceFontProvider | null = null
|
private fontProvider: TypefaceFontProvider | null = null
|
||||||
private fontsLoaded = false
|
private fontsLoaded = false
|
||||||
private imageCache = new Map<string, CKImage>()
|
private imageCache = new Map<string, CKImage>()
|
||||||
|
private vectorPathCache = new Map<string, Path>()
|
||||||
private rulerBgPaint: Paint
|
private rulerBgPaint: Paint
|
||||||
private rulerTickPaint: Paint
|
private rulerTickPaint: Paint
|
||||||
private rulerTextPaint: Paint
|
private rulerTextPaint: Paint
|
||||||
private rulerHlPaint: Paint
|
private rulerHlPaint: Paint
|
||||||
private rulerBadgePaint: Paint
|
private rulerBadgePaint: Paint
|
||||||
private rulerLabelPaint: Paint
|
private rulerLabelPaint: Paint
|
||||||
|
private penPathPaint: Paint
|
||||||
|
private penHandlePaint: Paint
|
||||||
|
private penVertexFill: Paint
|
||||||
|
private penVertexStroke: Paint
|
||||||
|
|
||||||
panX = 0
|
panX = 0
|
||||||
panY = 0
|
panY = 0
|
||||||
|
|
@ -233,6 +238,29 @@ export class SkiaRenderer {
|
||||||
this.rulerLabelPaint = new ck.Paint()
|
this.rulerLabelPaint = new ck.Paint()
|
||||||
this.rulerLabelPaint.setColor(ck.Color4f(1, 1, 1, 1))
|
this.rulerLabelPaint.setColor(ck.Color4f(1, 1, 1, 1))
|
||||||
this.rulerLabelPaint.setAntiAlias(true)
|
this.rulerLabelPaint.setAntiAlias(true)
|
||||||
|
|
||||||
|
this.penPathPaint = new ck.Paint()
|
||||||
|
this.penPathPaint.setStyle(ck.PaintStyle.Stroke)
|
||||||
|
this.penPathPaint.setStrokeWidth(PEN_PATH_STROKE_WIDTH)
|
||||||
|
this.penPathPaint.setColor(this.selColor())
|
||||||
|
this.penPathPaint.setAntiAlias(true)
|
||||||
|
|
||||||
|
this.penHandlePaint = new ck.Paint()
|
||||||
|
this.penHandlePaint.setStyle(ck.PaintStyle.Stroke)
|
||||||
|
this.penHandlePaint.setStrokeWidth(1)
|
||||||
|
this.penHandlePaint.setColor(this.selColor(PARENT_OUTLINE_ALPHA))
|
||||||
|
this.penHandlePaint.setAntiAlias(true)
|
||||||
|
|
||||||
|
this.penVertexFill = new ck.Paint()
|
||||||
|
this.penVertexFill.setStyle(ck.PaintStyle.Fill)
|
||||||
|
this.penVertexFill.setColor(ck.WHITE)
|
||||||
|
this.penVertexFill.setAntiAlias(true)
|
||||||
|
|
||||||
|
this.penVertexStroke = new ck.Paint()
|
||||||
|
this.penVertexStroke.setStyle(ck.PaintStyle.Stroke)
|
||||||
|
this.penVertexStroke.setStrokeWidth(PEN_PATH_STROKE_WIDTH)
|
||||||
|
this.penVertexStroke.setColor(this.selColor())
|
||||||
|
this.penVertexStroke.setAntiAlias(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
getFontProvider(): TypefaceFontProvider | null {
|
getFontProvider(): TypefaceFontProvider | null {
|
||||||
|
|
@ -934,6 +962,23 @@ export class SkiaRenderer {
|
||||||
canvas.restore()
|
canvas.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getVectorPath(node: SceneNode): Path | null {
|
||||||
|
if (!node.vectorNetwork) return null
|
||||||
|
const cached = this.vectorPathCache.get(node.id)
|
||||||
|
if (cached) return cached
|
||||||
|
const path = vectorNetworkToPath(this.ck, node.vectorNetwork)
|
||||||
|
this.vectorPathCache.set(node.id, path)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateVectorPath(nodeId: string): void {
|
||||||
|
const old = this.vectorPathCache.get(nodeId)
|
||||||
|
if (old) {
|
||||||
|
old.delete()
|
||||||
|
this.vectorPathCache.delete(nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private strokeNodeShape(canvas: Canvas, node: SceneNode, paint: Paint): void {
|
private strokeNodeShape(canvas: Canvas, node: SceneNode, paint: Paint): void {
|
||||||
const rect = this.ck.LTRBRect(0, 0, node.width, node.height)
|
const rect = this.ck.LTRBRect(0, 0, node.width, node.height)
|
||||||
|
|
||||||
|
|
@ -941,13 +986,11 @@ export class SkiaRenderer {
|
||||||
case 'ELLIPSE':
|
case 'ELLIPSE':
|
||||||
canvas.drawOval(rect, paint)
|
canvas.drawOval(rect, paint)
|
||||||
return
|
return
|
||||||
case 'VECTOR':
|
case 'VECTOR': {
|
||||||
if (node.vectorNetwork) {
|
const vp = this.getVectorPath(node)
|
||||||
const vp = vectorNetworkToPath(this.ck, node.vectorNetwork)
|
if (vp) canvas.drawPath(vp, paint)
|
||||||
canvas.drawPath(vp, paint)
|
|
||||||
vp.delete()
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
|
}
|
||||||
case 'LINE':
|
case 'LINE':
|
||||||
canvas.drawLine(0, 0, node.width, node.height, paint)
|
canvas.drawLine(0, 0, node.width, node.height, paint)
|
||||||
return
|
return
|
||||||
|
|
@ -1316,13 +1359,11 @@ export class SkiaRenderer {
|
||||||
hasRadius: boolean
|
hasRadius: boolean
|
||||||
): void {
|
): void {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'VECTOR':
|
case 'VECTOR': {
|
||||||
if (node.vectorNetwork) {
|
const vp = this.getVectorPath(node)
|
||||||
const vp = vectorNetworkToPath(this.ck, node.vectorNetwork)
|
if (vp) canvas.drawPath(vp, this.fillPaint)
|
||||||
canvas.drawPath(vp, this.fillPaint)
|
|
||||||
vp.delete()
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case 'ELLIPSE':
|
case 'ELLIPSE':
|
||||||
if (node.arcData) {
|
if (node.arcData) {
|
||||||
this.drawArc(canvas, node, this.fillPaint)
|
this.drawArc(canvas, node, this.fillPaint)
|
||||||
|
|
@ -1359,13 +1400,11 @@ export class SkiaRenderer {
|
||||||
hasRadius: boolean
|
hasRadius: boolean
|
||||||
): void {
|
): void {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'VECTOR':
|
case 'VECTOR': {
|
||||||
if (node.vectorNetwork) {
|
const vp = this.getVectorPath(node)
|
||||||
const vp = vectorNetworkToPath(this.ck, node.vectorNetwork)
|
if (vp) canvas.drawPath(vp, this.strokePaint)
|
||||||
canvas.drawPath(vp, this.strokePaint)
|
|
||||||
vp.delete()
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case 'ELLIPSE':
|
case 'ELLIPSE':
|
||||||
if (node.arcData) {
|
if (node.arcData) {
|
||||||
this.drawArc(canvas, node, this.strokePaint)
|
this.drawArc(canvas, node, this.strokePaint)
|
||||||
|
|
@ -1890,28 +1929,10 @@ export class SkiaRenderer {
|
||||||
if (!penState || penState.vertices.length === 0) return
|
if (!penState || penState.vertices.length === 0) return
|
||||||
|
|
||||||
const { vertices, segments, dragTangent, cursorX, cursorY } = penState
|
const { vertices, segments, dragTangent, cursorX, cursorY } = penState
|
||||||
const pathPaint = new this.ck.Paint()
|
const pathPaint = this.penPathPaint
|
||||||
pathPaint.setStyle(this.ck.PaintStyle.Stroke)
|
const handlePaint = this.penHandlePaint
|
||||||
pathPaint.setStrokeWidth(PEN_PATH_STROKE_WIDTH)
|
const vertexFill = this.penVertexFill
|
||||||
pathPaint.setColor(this.selColor())
|
const vertexStroke = this.penVertexStroke
|
||||||
pathPaint.setAntiAlias(true)
|
|
||||||
|
|
||||||
const handlePaint = new this.ck.Paint()
|
|
||||||
handlePaint.setStyle(this.ck.PaintStyle.Stroke)
|
|
||||||
handlePaint.setStrokeWidth(1)
|
|
||||||
handlePaint.setColor(this.selColor(PARENT_OUTLINE_ALPHA))
|
|
||||||
handlePaint.setAntiAlias(true)
|
|
||||||
|
|
||||||
const vertexFill = new this.ck.Paint()
|
|
||||||
vertexFill.setStyle(this.ck.PaintStyle.Fill)
|
|
||||||
vertexFill.setColor(this.ck.WHITE)
|
|
||||||
vertexFill.setAntiAlias(true)
|
|
||||||
|
|
||||||
const vertexStroke = new this.ck.Paint()
|
|
||||||
vertexStroke.setStyle(this.ck.PaintStyle.Stroke)
|
|
||||||
vertexStroke.setStrokeWidth(PEN_PATH_STROKE_WIDTH)
|
|
||||||
vertexStroke.setColor(this.selColor())
|
|
||||||
vertexStroke.setAntiAlias(true)
|
|
||||||
|
|
||||||
const toScreen = (x: number, y: number) => ({
|
const toScreen = (x: number, y: number) => ({
|
||||||
x: x * this.zoom + this.panX,
|
x: x * this.zoom + this.panX,
|
||||||
|
|
@ -2007,10 +2028,6 @@ export class SkiaRenderer {
|
||||||
canvas.drawCircle(v.x, v.y, radius, vertexStroke)
|
canvas.drawCircle(v.x, v.y, radius, vertexStroke)
|
||||||
}
|
}
|
||||||
|
|
||||||
pathPaint.delete()
|
|
||||||
handlePaint.delete()
|
|
||||||
vertexFill.delete()
|
|
||||||
vertexStroke.delete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Remote Cursors ---
|
// --- Remote Cursors ---
|
||||||
|
|
@ -2329,6 +2346,8 @@ export class SkiaRenderer {
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
for (const img of this.imageCache.values()) img.delete()
|
for (const img of this.imageCache.values()) img.delete()
|
||||||
this.imageCache.clear()
|
this.imageCache.clear()
|
||||||
|
for (const p of this.vectorPathCache.values()) p.delete()
|
||||||
|
this.vectorPathCache.clear()
|
||||||
this.fillPaint.delete()
|
this.fillPaint.delete()
|
||||||
this.strokePaint.delete()
|
this.strokePaint.delete()
|
||||||
this.selectionPaint.delete()
|
this.selectionPaint.delete()
|
||||||
|
|
@ -2347,6 +2366,10 @@ export class SkiaRenderer {
|
||||||
this.rulerHlPaint.delete()
|
this.rulerHlPaint.delete()
|
||||||
this.rulerBadgePaint.delete()
|
this.rulerBadgePaint.delete()
|
||||||
this.rulerLabelPaint.delete()
|
this.rulerLabelPaint.delete()
|
||||||
|
this.penPathPaint.delete()
|
||||||
|
this.penHandlePaint.delete()
|
||||||
|
this.penVertexFill.delete()
|
||||||
|
this.penVertexStroke.delete()
|
||||||
this.surface.delete()
|
this.surface.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -768,6 +768,9 @@ export function createEditorStore() {
|
||||||
|
|
||||||
function updateNode(id: string, changes: Partial<SceneNode>) {
|
function updateNode(id: string, changes: Partial<SceneNode>) {
|
||||||
graph.updateNode(id, changes)
|
graph.updateNode(id, changes)
|
||||||
|
if ('vectorNetwork' in changes) {
|
||||||
|
_renderer?.invalidateVectorPath(id)
|
||||||
|
}
|
||||||
runLayoutForNode(id)
|
runLayoutForNode(id)
|
||||||
syncIfInsideComponent(id)
|
syncIfInsideComponent(id)
|
||||||
requestRender()
|
requestRender()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue