diff --git a/index.d.ts b/index.d.ts index 4fbbd66d..d458dae8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,8 @@ /// +// Clear all type of caches in Skia +export function clearAllCache(): void + export interface DOMMatrix2DInit { a: number b: number diff --git a/index.js b/index.js index 06c8bf69..17804dd7 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const { platform, homedir } = require('os') const { join } = require('path') const { + clearAllCache, CanvasRenderingContext2D, CanvasElement, createContext, @@ -195,6 +196,7 @@ if (!process.env.DISABLE_SYSTEM_FONTS_LOAD) { } module.exports = { + clearAllCache, Canvas, createCanvas, Path2D, diff --git a/js-binding.js b/js-binding.js index 3eb6f14b..4256ead5 100644 --- a/js-binding.js +++ b/js-binding.js @@ -196,6 +196,7 @@ if (!nativeBinding) { } const { + clearAllCache, CanvasRenderingContext2D, CanvasElement, createContext, @@ -212,6 +213,7 @@ const { StrokeCap, } = nativeBinding +module.exports.clearAllCache = clearAllCache module.exports.CanvasRenderingContext2D = CanvasRenderingContext2D module.exports.CanvasElement = CanvasElement module.exports.createContext = createContext diff --git a/package.json b/package.json index fcbcf482..57ca6a5f 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "benny": "^3.7.1", "canvas": "^2.9.1", "canvaskit-wasm": "^0.35.0", - "colorette": "^2.0.16", + "colorette": "^2.0.19", "conventional-changelog-cli": "^2.2.2", "echarts": "^5.3.2", "eslint": "^8.16.0", @@ -91,7 +91,9 @@ "pinst": "^3.0.0", "png.js": "^0.2.1", "prettier": "^2.6.2", + "pretty-bytes": "^6.0.0", "skia-canvas": "^1.0.0", + "table": "^6.8.0", "typescript": "^4.7.2" }, "lint-staged": { diff --git a/scripts/debug-memory.mjs b/scripts/debug-memory.mjs new file mode 100644 index 00000000..94f44928 --- /dev/null +++ b/scripts/debug-memory.mjs @@ -0,0 +1,66 @@ +import { join } from 'node:path' +import { createRequire } from 'node:module' +import { setTimeout } from 'node:timers/promises' + +import { whiteBright, red, green, gray } from 'colorette' +import prettyBytes from 'pretty-bytes' +import { table } from 'table' + +import { createCanvas, Path2D, clearAllCache } from '../index.js' + +function paint() { + const require = createRequire(import.meta.url) + const tiger = require('../example/tiger.json') + const canvas = createCanvas(6016, 3384) + const ctx = canvas.getContext('2d') + for (const pathObject of tiger) { + const p = new Path2D(pathObject.d) + ctx.fillStyle = pathObject.fillStyle + ctx.strokeStyle = pathObject.strokeStyle + if (pathObject.lineWidth) { + ctx.lineWidth = parseInt(pathObject.lineWidth, 10) + } + ctx.stroke(p) + ctx.fill(p) + } +} + +const initial = process.memoryUsage() + +async function main() { + for (const [index, _] of Array.from({ length: 100 }).entries()) { + displayMemoryUsageFromNode(initial) + await setTimeout(100) + global?.gc?.() + await paint() + } +} + +main().then(() => { + displayMemoryUsageFromNode(initial) + clearAllCache() + global?.gc?.() + setInterval(() => { + displayMemoryUsageFromNode(initial) + }, 2000) +}) + +function displayMemoryUsageFromNode(initialMemoryUsage) { + const finalMemoryUsage = process.memoryUsage() + const titles = Object.keys(initialMemoryUsage).map((k) => whiteBright(k)) + const tableData = [titles] + const diffColumn = [] + for (const [key, value] of Object.entries(initialMemoryUsage)) { + const diff = finalMemoryUsage[key] - value + const prettyDiff = prettyBytes(diff, { signed: true }) + if (diff > 0) { + diffColumn.push(red(prettyDiff)) + } else if (diff < 0) { + diffColumn.push(green(prettyDiff)) + } else { + diffColumn.push(gray(prettyDiff)) + } + } + tableData.push(diffColumn) + console.info(table(tableData)) +} diff --git a/scripts/utils.js b/scripts/utils.js index 6387fbe4..84be2c12 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -7,7 +7,7 @@ const REPO = 'canvas' const [FULL_HASH] = process.env.NODE_ENV === 'ava' ? ['000000'] : execSync(`git submodule status skia`).toString('utf8').trim().split(' ') -const SHORT_HASH = FULL_HASH.substr(0, 8) +const SHORT_HASH = FULL_HASH.substring(0, 8) const TAG = `skia-${SHORT_HASH}` diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index ee0074a8..69d77018 100644 --- a/skia-c/skia_c.cpp +++ b/skia-c/skia_c.cpp @@ -56,6 +56,11 @@ extern "C" }; } + void skiac_clear_all_cache() + { + SkGraphics::PurgeAllCaches(); + } + // Surface static SkSurface *skiac_surface_create(int width, int height, SkAlphaType alphaType, uint8_t cs) diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp index 09886328..fbb48a43 100644 --- a/skia-c/skia_c.hpp +++ b/skia-c/skia_c.hpp @@ -211,7 +211,7 @@ struct skiac_mapped_point extern "C" { - + void skiac_clear_all_cache(); // Surface skiac_surface *skiac_surface_create_rgba_premultiplied(int width, int height, uint8_t cs); void skiac_surface_create_svg(skiac_svg_surface *c_surface, int width, int height, int alphaType, uint32_t flag, uint8_t cs); diff --git a/src/ctx.rs b/src/ctx.rs index 7ea470e2..df0c40e2 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -42,6 +42,7 @@ impl ImageOrCanvas { } pub struct Context { + env: Env, pub(crate) surface: Surface, path: Path, pub alpha: bool, @@ -53,6 +54,17 @@ pub struct Context { pub stream: Option, } +impl Drop for Context { + fn drop(&mut self) { + if let Err(e) = self + .env + .adjust_external_memory(-((self.width * self.height * 4) as i64)) + { + eprintln!("{e}"); + } + } +} + impl Context { pub fn create_js_class(env: &Env) -> Result { env.define_class( @@ -242,6 +254,7 @@ impl Context { } pub fn new_svg( + env: Env, width: u32, height: u32, svg_export_flag: SvgExportFlag, @@ -256,6 +269,7 @@ impl Context { ) .ok_or_else(|| Error::from_reason("Create skia svg surface failed".to_owned()))?; Ok(Context { + env, surface, alpha: true, path: Path::new(), @@ -268,10 +282,11 @@ impl Context { }) } - pub fn new(width: u32, height: u32, color_space: ColorSpace) -> Result { + pub fn new(env: Env, width: u32, height: u32, color_space: ColorSpace) -> Result { let surface = Surface::new_rgba_premultiplied(width, height, color_space) .ok_or_else(|| Error::from_reason("Create skia surface failed".to_owned()))?; Ok(Context { + env, surface, alpha: true, path: Path::new(), @@ -845,12 +860,21 @@ fn context_2d_constructor(ctx: CallContext) -> Result { let mut this = ctx.this_unchecked::(); let context_2d = if ctx.length == 3 { - Context::new(width, height, color_space)? + Context::new(*ctx.env, width, height, color_space)? } else { // SVG Canvas let flag = ctx.get::(3)?.get_uint32()?; - Context::new_svg(width, height, SvgExportFlag::try_from(flag)?, color_space)? + Context::new_svg( + *ctx.env, + width, + height, + SvgExportFlag::try_from(flag)?, + color_space, + )? }; + ctx + .env + .adjust_external_memory((width * height * 4) as i64)?; ctx.env.wrap(&mut this, context_2d)?; ctx.env.get_undefined() } diff --git a/src/lib.rs b/src/lib.rs index 1f68e473..b2e3015f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -417,3 +417,8 @@ impl SVGCanvas { } } } + +#[napi] +pub fn clear_all_cache() { + unsafe { sk::ffi::skiac_clear_all_cache() }; +} diff --git a/src/sk.rs b/src/sk.rs index 54d0fb6a..712f67ee 100644 --- a/src/sk.rs +++ b/src/sk.rs @@ -13,7 +13,7 @@ use crate::error::SkError; use crate::font::{FontStretch, FontStyle}; use crate::image::ImageData; -mod ffi { +pub mod ffi { use std::ffi::c_void; use std::os::raw::c_char; @@ -247,6 +247,8 @@ mod ffi { #[link(name = "skiac", kind = "static", cfg(target_os = "windows"))] extern "C" { + pub fn skiac_clear_all_cache(); + pub fn skiac_surface_create_rgba_premultiplied( width: i32, height: i32, diff --git a/yarn.lock b/yarn.lock index 4205d64a..0e5607e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -244,7 +244,7 @@ __metadata: benny: ^3.7.1 canvas: ^2.9.1 canvaskit-wasm: ^0.35.0 - colorette: ^2.0.16 + colorette: ^2.0.19 conventional-changelog-cli: ^2.2.2 echarts: ^5.3.2 eslint: ^8.16.0 @@ -259,7 +259,9 @@ __metadata: pinst: ^3.0.0 png.js: ^0.2.1 prettier: ^2.6.2 + pretty-bytes: ^6.0.0 skia-canvas: ^1.0.0 + table: ^6.8.0 typescript: ^4.7.2 languageName: unknown linkType: soft @@ -928,6 +930,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:^8.0.1": + version: 8.11.0 + resolution: "ajv@npm:8.11.0" + dependencies: + fast-deep-equal: ^3.1.1 + json-schema-traverse: ^1.0.0 + require-from-string: ^2.0.2 + uri-js: ^4.2.2 + checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + languageName: node + linkType: hard + "ansi-escapes@npm:^4.3.0": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -1579,7 +1593,7 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.16, colorette@npm:^2.0.17": +"colorette@npm:^2.0.16, colorette@npm:^2.0.17, colorette@npm:^2.0.19": version: 2.0.19 resolution: "colorette@npm:2.0.19" checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427 @@ -3548,6 +3562,13 @@ __metadata: languageName: node linkType: hard +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -3781,6 +3802,13 @@ __metadata: languageName: node linkType: hard +"lodash.truncate@npm:^4.4.2": + version: 4.4.2 + resolution: "lodash.truncate@npm:4.4.2" + checksum: b463d8a382cfb5f0e71c504dcb6f807a7bd379ff1ea216669aa42c52fc28c54e404bfbd96791aa09e6df0de2c1d7b8f1b7f4b1a61f324d38fe98bc535aeee4f5 + languageName: node + linkType: hard + "lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -4836,6 +4864,13 @@ __metadata: languageName: node linkType: hard +"pretty-bytes@npm:^6.0.0": + version: 6.0.0 + resolution: "pretty-bytes@npm:6.0.0" + checksum: 0bb9f95e617236404b29a8392c6efd82d65805f622f5e809ecd70068102be857d4e3276c86d2a32fa2ef851cc29472e380945dab7bec83ec79bd57a19a10faf7 + languageName: node + linkType: hard + "pretty-ms@npm:^7.0.1": version: 7.0.1 resolution: "pretty-ms@npm:7.0.1" @@ -5025,6 +5060,13 @@ __metadata: languageName: node linkType: hard +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: a03ef6895445f33a4015300c426699bc66b2b044ba7b670aa238610381b56d3f07c686251740d575e22f4c87531ba662d06937508f0f3c0f1ddc04db3130560b + languageName: node + linkType: hard + "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0" @@ -5649,6 +5691,19 @@ __metadata: languageName: node linkType: hard +"table@npm:^6.8.0": + version: 6.8.0 + resolution: "table@npm:6.8.0" + dependencies: + ajv: ^8.0.1 + lodash.truncate: ^4.4.2 + slice-ansi: ^4.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + checksum: 5b07fe462ee03d2e1fac02cbb578efd2e0b55ac07e3d3db2e950aa9570ade5a4a2b8d3c15e9f25c89e4e50b646bc4269934601ee1eef4ca7968ad31960977690 + languageName: node + linkType: hard + "tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.11 resolution: "tar@npm:6.1.11"