fix(renderer): delete CanvasKit Shader/PathEffect/ColorFilter objects to prevent WASM heap OOM#128
Open
Postroggy wants to merge 1 commit into
Open
Conversation
… to prevent WASM heap OOM CanvasKit uses manual memory management — every object created via the CanvasKit API must be explicitly `.delete()`d. When a JS-side reference to a CanvasKit object is held (e.g. `const shader = ck.Shader.Make...()`), calling `paint.delete()` only decrements the paint's own ref count on the WASM-side object; the JS wrapper still holds a ref, so the WASM heap object is never freed. This caused a linear WASM heap growth during batch rendering (e.g. offline .fig export), crashing with `RuntimeError: Aborted()` after ~100 nodes. Fix: call `.delete()` on the JS-side handle immediately after handing ownership to the paint via `set*()`. The paint holds its own internal ref (via SkRef), so deleting the JS wrapper is safe and correct. Affected sites (13 total): - `makeFillPaint`: linear gradient Shader, radial gradient Shader - `applyImageFillToPaint`: tile image Shader, ColorFilter from buildImageAdjustmentFilter - `drawImageFillRect`: ColorFilter from buildImageAdjustmentFilter - `makeStrokePaint`: PathEffect.MakeDash - `drawEllipse`: PathEffect.MakeCorner ×2 (fill + stroke) - `drawPolygon`: PathEffect.MakeCorner ×2 (fill + stroke) - `drawImage`: ColorFilter from buildImageAdjustmentFilter, tile Shader - `drawImageFallback`: PathEffect.MakeDash
Contributor
|
Thank you for your submission, but we are rewriting this program using Rust, so your submission may be temporarily irrelevant. You can refer to the v0.8.0 branch if you're interested. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When rendering a large number of nodes (e.g. 800+ component variants from a Figma library), the CanvasKit WASM heap grows linearly until it is exhausted, crashing with:
The crash occurs after rendering ~100–200 nodes depending on component complexity.
Root Cause
CanvasKit uses manual ref-counted memory on the WASM heap. Every object created via
ck.Shader.MakeLinearGradient(...),ck.PathEffect.MakeDash(...),ck.ColorFilter.MakeMatrix(...), etc. must be explicitly.delete()d.The bug: after calling
paint.setShader(shader)/paint.setPathEffect(effect)/paint.setColorFilter(cf), the JS-side handle was never deleted.paint.delete()only decrements the Paint's own ref — the underlying Skia object held by the JS variable is never freed.Internally,
paint.setShader(shader)callsSkRef()on the C++ side (refcount → 2). When the paint is deleted, refcount drops to 1. Withoutshader.delete(), the WASM heap object is permanently leaked. Across hundreds of render calls this exhausts the WASM heap.Fix
Add
.delete()immediately after eachset*()call — safe because the Paint holds its own internalSkRef. 13 sites fixed innode-renderer.ts:makeFillPaintShader(linear gradient)makeFillPaintShader(radial gradient)applyImageFillToPaintShader(tile image)applyImageFillToPaintColorFilter(image adjust)drawImageFillRectColorFilter(image adjust)makeStrokePaintPathEffect(dash)drawEllipsePathEffect(corner) × 2drawPolygonPathEffect(corner) × 2drawImageColorFilter(image adjust)drawImageShader(tile image)drawImageFallbackPathEffect(dash)Pattern applied
Verification
Tested rendering 800+ Figma component variants in a single batch run. Before fix: crash at ~181 nodes. After fix: all nodes render successfully with stable memory usage.