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 }