Reuse GL context on panel resize
Panel resizing now creates a new surface from the existing GrDirectContext instead of destroying and recreating the entire renderer. All caches (paints, fonts, node pictures, scene picture, filters) are preserved across resizes.
This commit is contained in:
parent
921f28e9f9
commit
93c549bad0
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -50,4 +50,3 @@ input[type='number']::-webkit-inner-spin-button,
|
|||
input[type='number']::-webkit-outer-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ const cursor = computed(() => {
|
|||
<div
|
||||
class="absolute bottom-1/2 left-1/2 h-0.5 w-25 -translate-x-1/2 translate-y-10 overflow-hidden rounded-full bg-white/8"
|
||||
>
|
||||
<div class="h-full w-2/5 animate-[slide_1s_ease-in-out_infinite] rounded-full bg-white/25" />
|
||||
<div
|
||||
class="h-full w-2/5 animate-[slide_1s_ease-in-out_infinite] rounded-full bg-white/25"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export function useCanvas(canvasRef: Ref<HTMLCanvasElement | null>, store: Edito
|
|||
let renderer: SkiaRenderer | null = null
|
||||
let ck: CanvasKit | null = null
|
||||
let gpuCtx: WebGPUContext | null = null
|
||||
let glContext: ReturnType<CanvasKit['MakeGrContext']> | null = null
|
||||
let destroyed = false
|
||||
let dirty = true
|
||||
let lastRenderVersion = -1
|
||||
|
|
@ -71,17 +72,34 @@ export function useCanvas(canvasRef: Ref<HTMLCanvasElement | null>, 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<HTMLCanvasElement | null>, 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<HTMLCanvasElement | null>, store: Edito
|
|||
pause()
|
||||
cancelAnimationFrame(resizeRaf)
|
||||
renderer?.destroy()
|
||||
glContext?.delete()
|
||||
})
|
||||
|
||||
let resizeRaf = 0
|
||||
|
|
@ -179,10 +193,27 @@ export function useCanvas(canvasRef: Ref<HTMLCanvasElement | null>, 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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue