Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: optimize canvas renderer performance #1810

Merged
merged 10 commits into from
Nov 4, 2024
12 changes: 12 additions & 0 deletions .changeset/large-moose-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@antv/g-plugin-canvas-path-generator': minor
'@antv/g-plugin-canvaskit-renderer': minor
'@antv/g-plugin-canvas-renderer': minor
'@antv/g-plugin-device-renderer': minor
'@antv/g-plugin-canvas-picker': minor
'@antv/g-plugin-image-loader': minor
'@antv/g-plugin-svg-renderer': minor
'@antv/g-lite': minor
---

perf: optimize canvas renderer performance
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ module.exports = {
{ functions: false, classes: false },
],
'@typescript-eslint/no-redeclare': ['error'],
'@typescript-eslint/no-this-alias': ['error', { allowedNames: ['self'] }],
'@typescript-eslint/restrict-template-expressions': 'warn',
'@typescript-eslint/return-await': 'warn',
'@typescript-eslint/default-param-last': 'warn',
Expand Down
2 changes: 1 addition & 1 deletion __tests__/demos/bugfix/1760.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Canvas, Path, Line } from '@antv/g';
/**
* @see https://github.com/antvis/G/issues/1760
* @see https://github.com/antvis/G/issues/1790
* @see https://github.com/antvis/G/pull/1808
* @see https://github.com/antvis/G/pull/1809
*/
export async function issue_1760(context: { canvas: Canvas }) {
const { canvas } = context;
Expand Down
105 changes: 105 additions & 0 deletions __tests__/demos/perf/attr-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as lil from 'lil-gui';
import { Rect, Group, CanvasEvent } from '@antv/g';
import type { Canvas } from '@antv/g';

export async function attrUpdate(context: { canvas: Canvas; gui: lil.GUI }) {
const { canvas, gui } = context;
console.log(canvas);

await canvas.ready;

const { width, height } = canvas.getConfig();
const count = 2e4;
const root = new Group();
const rects = [];

const perfStore: { [k: string]: { count: number; time: number } } = {
update: { count: 0, time: 0 },
setAttribute: { count: 0, time: 0 },
};

function updatePerf(key: string, time: number) {
Dismissed Show dismissed Hide dismissed
perfStore[key].count++;
perfStore[key].time += time;
console.log(
`average ${key} time: `,
perfStore[key].time / perfStore[key].count,
);
}

function update() {
// const startTime = performance.now();
// console.time('update');

const rectsToRemove = [];

// const startTime0 = performance.now();
// console.time('setAttribute');
for (let i = 0; i < count; i++) {
const rect = rects[i];
rect.x -= rect.speed;
(rect.el as Rect).setAttribute('x', rect.x);
if (rect.x + rect.size < 0) rectsToRemove.push(i);
}
// console.timeEnd('setAttribute');
// updatePerf('setAttribute', performance.now() - startTime0);

rectsToRemove.forEach((i) => {
rects[i].x = width + rects[i].size / 2;
});

// console.timeEnd('update');
// updatePerf('update', performance.now() - startTime);
}

function render() {
for (let i = 0; i < count; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const size = 10 + Math.random() * 40;
const speed = 1 + Math.random();

const rect = new Rect({
style: {
x,
y,
width: size,
height: size,
fill: 'white',
stroke: 'black',
},
});
root.appendChild(rect);
rects[i] = { x, y, size, speed, el: rect };
}
}

render();
canvas.addEventListener(CanvasEvent.BEFORE_RENDER, () => update());

canvas.appendChild(root);

canvas.addEventListener(
'rerender',
() => {
// console.timeEnd('render');
},
{ once: true },
);

// GUI
canvas.getConfig().renderer.getConfig().enableRenderingOptimization = true;

gui
.add(
{
enableRenderingOptimization: canvas.getConfig().renderer.getConfig()
.enableRenderingOptimization,
},
'enableRenderingOptimization',
)
.onChange((result) => {
canvas.getConfig().renderer.getConfig().enableRenderingOptimization =
result;
});
}
1 change: 1 addition & 0 deletions __tests__/demos/perf/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { circles } from './circles';
export { rects } from './rect';
export { image } from './image';
export { attrUpdate } from './attr-update';
24 changes: 12 additions & 12 deletions __tests__/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// import Stats from 'stats.js';
import Stats from 'stats.js';
import * as lil from 'lil-gui';
import '@antv/g-camera-api';
import { Canvas, CanvasEvent, runtime } from '@antv/g';
Expand Down Expand Up @@ -46,7 +46,7 @@ const renderers = {
};
const app = document.getElementById('app') as HTMLElement;
let currentContainer = document.createElement('div');
let canvas;
let canvas: Canvas;
let prevAfter;
const normalizeName = (name: string) => name.replace(/-/g, '').toLowerCase();
const renderOptions = (keyword = '') => {
Expand Down Expand Up @@ -227,23 +227,23 @@ function createSpecRender(object) {
window.__g_instances__ = [canvas];

// stats
// const stats = new Stats();
// stats.showPanel(0);
// const $stats = stats.dom;
// $stats.style.position = 'absolute';
// $stats.style.left = '4px';
// $stats.style.top = '4px';
// app.appendChild($stats);
const stats = new Stats();
stats.showPanel(0);
const $stats = stats.dom;
$stats.style.position = 'fixed';
$stats.style.left = '2px';
$stats.style.top = '2px';
// document.body.appendChild($stats);

// GUI
const gui = new lil.GUI({ autoPlace: false });
$div.appendChild(gui.domElement);

await generate({ canvas, renderer, container: $div, gui });

// canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => {
// stats.update();
// });
canvas.addEventListener(CanvasEvent.AFTER_RENDER, () => {
stats.update();
});

if (
selectRenderer.value === 'canvas' &&
Expand Down
1 change: 1 addition & 0 deletions packages/g-lite/src/AbstractRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class AbstractRenderer implements IRenderer {
enableDirtyRectangleRendering: true,
enableDirtyRectangleRenderingDebug: false,
enableSizeAttenuation: true,
enableRenderingOptimization: false,
...config,
};
}
Expand Down
27 changes: 17 additions & 10 deletions packages/g-lite/src/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ import {
} from './camera';
import type { RBushNodeAABB } from './components';
import type { CustomElement } from './display-objects';
import { DisplayObject } from './display-objects/DisplayObject';
import type { MutationEvent } from './dom/MutationEvent';
import {
DisplayObject,
attrModifiedEvent as attrModifiedEventCache,
} from './display-objects/DisplayObject';
import {
insertedEvent as insertedEventCache,
removedEvent as removedEventCache,
destroyEvent as destroyEventCache,
} from './dom/Element';
import type { CanvasContext, Element, IChildNode } from './dom';
import { CustomEvent, Document, ElementEvent, EventTarget } from './dom';
import { CustomElementRegistry } from './dom/CustomElementRegistry';
Expand Down Expand Up @@ -122,9 +131,6 @@ export class Canvas extends EventTarget implements ICanvas {
*/
isMouseEvent: (event: InteractivePointerEvent) => event is MouseEvent;

/**
* double click speed (ms), default is 200ms
*/
dblClickSpeed?: CanvasConfig['dblClickSpeed'];

/**
Expand Down Expand Up @@ -398,9 +404,7 @@ export class Canvas extends EventTarget implements ICanvas {
this.dispatchEvent(new CustomEvent(CanvasEvent.BEFORE_DESTROY));
}
if (this.frameId) {
const cancelRAF =
this.getConfig().cancelAnimationFrame || cancelAnimationFrame;
cancelRAF(this.frameId);
this.cancelAnimationFrame(this.frameId);
}

// unmount all children
Expand Down Expand Up @@ -429,19 +433,22 @@ export class Canvas extends EventTarget implements ICanvas {
this.dispatchEvent(new CustomEvent(CanvasEvent.AFTER_DESTROY));
}

const clearEventRetain = (event: CustomEvent) => {
const clearEventRetain = (event: CustomEvent | MutationEvent) => {
event.currentTarget = null;
event.manager = null;
event.target = null;
(event as MutationEvent).relatedNode = null;
};

clearEventRetain(mountedEvent);
clearEventRetain(unmountedEvent);
clearEventRetain(beforeRenderEvent);
clearEventRetain(rerenderEvent);
clearEventRetain(afterRenderEvent);

this.cancelAnimationFrame(this.frameId);
clearEventRetain(attrModifiedEventCache);
clearEventRetain(insertedEventCache);
clearEventRetain(removedEventCache);
clearEventRetain(destroyEventCache);
}

/**
Expand Down
25 changes: 11 additions & 14 deletions packages/g-lite/src/css/StyleValueRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import { EMPTY_PARSED_PATH } from '../display-objects/constants';
import type { GlobalRuntime } from '../global-runtime';
import { GeometryAABBUpdater } from '../services';
import { AABB } from '../shapes';
import type {
BaseStyleProps,
ParsedBaseStyleProps,
Tuple3Number,
} from '../types';
import type { BaseStyleProps, Tuple3Number } from '../types';
import { Shape } from '../types';
import type { CSSRGB } from './cssom';
import type {
Expand Down Expand Up @@ -615,8 +611,8 @@ export const BUILT_IN_PROPERTIES: PropertyMetadata[] = [
},
];

const GEOMETRY_ATTRIBUTE_NAMES = BUILT_IN_PROPERTIES.filter((n) => !!n.l).map(
(n) => n.n,
const GEOMETRY_ATTRIBUTE_NAMES = new Set(
BUILT_IN_PROPERTIES.filter((n) => !!n.l).map((n) => n.n),
);

export const propertyMetadataCache: Record<string, PropertyMetadata> = {};
Expand Down Expand Up @@ -667,12 +663,13 @@ export class DefaultStyleValueRegistry implements StyleValueRegistry {
Object.assign(object.parsedStyle, attributes);

let needUpdateGeometry = !!options.forceUpdateGeometry;

if (
!needUpdateGeometry &&
GEOMETRY_ATTRIBUTE_NAMES.some((name) => name in attributes)
) {
needUpdateGeometry = true;
if (!needUpdateGeometry) {
for (const i in attributes) {
if (GEOMETRY_ATTRIBUTE_NAMES.has(i)) {
needUpdateGeometry = true;
break;
}
}
}

if (attributes.fill) {
Expand Down Expand Up @@ -858,7 +855,7 @@ export class DefaultStyleValueRegistry implements StyleValueRegistry {
if (!geometry.renderBounds) {
geometry.renderBounds = new AABB();
}
const parsedStyle = object.parsedStyle as ParsedBaseStyleProps;
const parsedStyle = object.parsedStyle;
const {
cx = 0,
cy = 0,
Expand Down
4 changes: 2 additions & 2 deletions packages/g-lite/src/css/parser/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ export function isCSSRGB(object: any): object is CSSRGB {
* @see https://github.com/WebKit/WebKit/blob/main/Source/WebCore/css/parser/CSSParser.cpp#L97
*/
export const parseColor = memoize(
(colorStr: string): CSSRGB | CSSGradientValue[] | Pattern => {
(colorStr: string | Pattern): CSSRGB | CSSGradientValue[] | Pattern => {
if (isPattern(colorStr)) {
return {
repetition: 'repeat',
...(colorStr as Pattern),
...colorStr,
};
}

Expand Down
8 changes: 2 additions & 6 deletions packages/g-lite/src/css/parser/transform-origin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,8 @@ export const parseTransformOrigin = memoize(

// eg. center bottom
return [
parseLengthOrPercentage(
convertKeyword2Percent(values[0]),
) as CSSUnitValue,
parseLengthOrPercentage(
convertKeyword2Percent(values[1]),
) as CSSUnitValue,
parseLengthOrPercentage(convertKeyword2Percent(values[0])),
parseLengthOrPercentage(convertKeyword2Percent(values[1])),
];
}
return [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { DisplayObject } from '../../display-objects';
import { ParsedBaseStyleProps } from '../../types';
import { UnitType, type CSSUnitValue } from '../cssom';
import type { CSSProperty } from '../CSSProperty';

Expand All @@ -15,7 +14,7 @@ export class CSSPropertyTransformOrigin
>
{
postProcessor(object: DisplayObject) {
const { transformOrigin } = object.parsedStyle as ParsedBaseStyleProps;
const { transformOrigin } = object.parsedStyle;
if (
transformOrigin[0].unit === UnitType.kPixels &&
transformOrigin[1].unit === UnitType.kPixels
Expand Down
Loading
Loading