diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c3c2abeb..18280289f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,7 @@
- Per-node SkPicture cache for effect rendering — unchanged shadow/blur nodes replay from cache on scene redraws
- Drop shadows use `MaskFilter` direct draw instead of `saveLayer` offscreen buffers
- Cached `ImageFilter`, `MaskFilter`, reusable effect paint — zero per-frame WASM allocations for effects
+- Reuse GL context on panel resize — swap surface without recreating renderer, preserving all caches
- Per-frame absolute position cache — avoids repeated parent-chain walks during rendering
- Optimize zoom/pan smoothness with `shallowReactive`, `useRafFn`, and input coalescing
diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts
index 33a3726da..1ce714990 100644
--- a/packages/core/src/renderer.ts
+++ b/packages/core/src/renderer.ts
@@ -328,6 +328,12 @@ export class SkiaRenderer {
this.invalidateAllPictures()
}
+ replaceSurface(surface: Surface): void {
+ this.surface.delete()
+ this.surface = surface
+ this.invalidateScenePicture()
+ }
+
invalidateScenePicture(): void {
this.scenePicture?.delete()
this.scenePicture = null
diff --git a/src/app.css b/src/app.css
index 7ff7e2008..cf8dcde9d 100644
--- a/src/app.css
+++ b/src/app.css
@@ -50,4 +50,3 @@ input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
display: none;
}
-
diff --git a/src/components/EditorCanvas.vue b/src/components/EditorCanvas.vue
index 6639c6cd9..f8e97df3d 100644
--- a/src/components/EditorCanvas.vue
+++ b/src/components/EditorCanvas.vue
@@ -63,7 +63,9 @@ const cursor = computed(() => {
diff --git a/src/composables/use-canvas.ts b/src/composables/use-canvas.ts
index f420f8c2c..4f0f12139 100644
--- a/src/composables/use-canvas.ts
+++ b/src/composables/use-canvas.ts
@@ -41,6 +41,7 @@ export function useCanvas(canvasRef: Ref, store: Edito
let renderer: SkiaRenderer | null = null
let ck: CanvasKit | null = null
let gpuCtx: WebGPUContext | null = null
+ let glContext: ReturnType | null = null
let destroyed = false
let dirty = true
let lastRenderVersion = -1
@@ -71,17 +72,34 @@ export function useCanvas(canvasRef: Ref, store: Edito
}
}
+ function sizeCanvas(canvas: HTMLCanvasElement) {
+ const dpr = window.devicePixelRatio || 1
+ canvas.width = canvas.clientWidth * dpr
+ canvas.height = canvas.clientHeight * dpr
+ }
+
+ function makeGLSurface(canvas: HTMLCanvasElement) {
+ if (!ck) return null
+ if (!glContext) {
+ const isTest = new URLSearchParams(window.location.search).has('test')
+ const glAttrs = isTest ? { preserveDrawingBuffer: 1 } : undefined
+ const handle = ck.GetWebGLContext(canvas, glAttrs)
+ if (!handle) return null
+ glContext = ck.MakeGrContext(handle)
+ }
+ if (!glContext) return null
+ return ck.MakeOnScreenGLSurface(glContext, canvas.width, canvas.height, ck.ColorSpace.SRGB)
+ }
+
function createSurface(canvas: HTMLCanvasElement) {
if (!ck) return
renderer?.destroy()
renderer = null
+ glContext?.delete()
+ glContext = null
- const dpr = window.devicePixelRatio || 1
- const w = canvas.clientWidth
- const h = canvas.clientHeight
- canvas.width = w * dpr
- canvas.height = h * dpr
+ sizeCanvas(canvas)
let surface
if (getGpuBackend() === 'webgpu' && gpuCtx) {
@@ -93,12 +111,7 @@ export function useCanvas(canvasRef: Ref, store: Edito
return
}
} else {
- const isTest = new URLSearchParams(window.location.search).has('test')
- surface = ck.MakeWebGLCanvasSurface(
- canvas,
- undefined,
- isTest ? { preserveDrawingBuffer: 1 } : undefined
- )
+ surface = makeGLSurface(canvas)
if (!surface) {
console.error('Failed to create WebGL surface')
return
@@ -171,6 +184,7 @@ export function useCanvas(canvasRef: Ref, store: Edito
pause()
cancelAnimationFrame(resizeRaf)
renderer?.destroy()
+ glContext?.delete()
})
let resizeRaf = 0
@@ -179,10 +193,27 @@ export function useCanvas(canvasRef: Ref, store: Edito
if (!canvas || !ck || resizeRaf) return
resizeRaf = requestAnimationFrame(() => {
resizeRaf = 0
- createSurface(canvas)
+ resizeCanvas(canvas)
})
})
+ function resizeCanvas(canvas: HTMLCanvasElement) {
+ if (!ck || !renderer) {
+ createSurface(canvas)
+ return
+ }
+
+ sizeCanvas(canvas)
+
+ const surface = makeGLSurface(canvas)
+ if (!surface) {
+ createSurface(canvas)
+ return
+ }
+ renderer.replaceSurface(surface)
+ renderNow()
+ }
+
function hitTestSectionTitle(canvasX: number, canvasY: number) {
return renderer?.hitTestSectionTitle(store.graph, canvasX, canvasY) ?? null
}