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

Scene Graph performance improvements #2858

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/paper/src/Examples/Matrix/Matrix.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BlurMask,
Canvas,
Canvas2,
Fill,
Group,
useClock,
Expand Down Expand Up @@ -41,7 +42,7 @@ export const Matrix = () => {
}
const symbols = font.getGlyphIDs("abcdefghijklmnopqrstuvwxyz");
return (
<Canvas style={{ flex: 1 }} opaque>
<Canvas2 style={{ flex: 1 }} opaque>
<Fill color="black" />
<Group>
<BlurMask blur={8} style="solid" />
Expand All @@ -60,6 +61,6 @@ export const Matrix = () => {
))
)}
</Group>
</Canvas>
</Canvas2>
);
};
6 changes: 3 additions & 3 deletions apps/paper/src/Examples/Matrix/Symbol.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { interpolateColors, vec, Glyphs } from "@shopify/react-native-skia";
import type { SharedValue } from "react-native-reanimated";
import { useDerivedValue } from "react-native-reanimated";

export const COLS = 8;
export const ROWS = 15;
export const COLS = 32;
export const ROWS = 64;
const pos = vec(0, 0);

interface SymbolProps {
Expand Down Expand Up @@ -38,7 +38,7 @@ export const Symbol = ({
}, [timestamp]);

const opacity = useDerivedValue(() => {
const idx = Math.round(timestamp.value / 100);
const idx = Math.round(timestamp.value / 75);
return stream[(stream.length - j + idx) % stream.length];
}, [timestamp]);

Expand Down
9 changes: 8 additions & 1 deletion packages/skia/cpp/api/JsiSkPaint.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class JsiSkPaint : public JsiSkWrappingSharedPtrHostObject<SkPaint> {
public:
EXPORT_JSI_API_TYPENAME(JsiSkPaint, Paint)

JSI_HOST_FUNCTION(assign) {
SkPaint* paint = JsiSkPaint::fromValue(runtime, arguments[0]).get();
*getObject() = *paint;
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION(copy) {
const auto *paint = getObject().get();
return jsi::Object::createFromHostObject(
Expand Down Expand Up @@ -163,7 +169,8 @@ class JsiSkPaint : public JsiSkWrappingSharedPtrHostObject<SkPaint> {
return jsi::Value::undefined();
}

JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkPaint, copy),
JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkPaint, assign),
JSI_EXPORT_FUNC(JsiSkPaint, copy),
JSI_EXPORT_FUNC(JsiSkPaint, reset),
JSI_EXPORT_FUNC(JsiSkPaint, getAlphaf),
JSI_EXPORT_FUNC(JsiSkPaint, getColor),
Expand Down
2 changes: 2 additions & 0 deletions packages/skia/src/skia/types/Paint/Paint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export interface SkPaint extends SkJSIInstance<"Paint"> {
*/
reset(): void;

assign(paint: SkPaint): void;

/**
* Retrieves the alpha and RGB unpremultiplied. RGB are extended sRGB values
* (sRGB gamut, and encoded with the sRGB transfer function).
Expand Down
4 changes: 4 additions & 0 deletions packages/skia/src/skia/web/JsiSkPaint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class JsiSkPaint extends HostObject<Paint, "Paint"> implements SkPaint {
return new JsiSkPaint(this.CanvasKit, this.ref.copy());
}

assign(paint: JsiSkPaint) {
this.ref = paint.ref.copy();
}

reset() {
this.ref = new this.CanvasKit.Paint();
}
Expand Down
30 changes: 20 additions & 10 deletions packages/skia/src/sksg/Container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,35 @@ import {
HAS_REANIMATED_3,
} from "../external/reanimated/renderHelpers";

import type { StaticContext } from "./StaticContext";
import { createStaticContext } from "./StaticContext";
import { createDrawingContext } from "./DrawingContext";
import type { Node } from "./nodes";
import { draw, isSharedValue } from "./nodes";

const drawOnscreen = (Skia: Skia, nativeId: number, root: Node[]) => {
const drawOnscreen = (
Skia: Skia,
nativeId: number,
root: Node[],
staticCtx: StaticContext
) => {
"worklet";
const rec = Skia.PictureRecorder();
const canvas = rec.beginRecording();
// TODO: This is only support from 3.15 and above (check the exact version)
// This could be polyfilled in C++ if needed (or in JS via functions only?)
const ctx = createDrawingContext(Skia, canvas);
const start = performance.now();
const ctx = createDrawingContext(Skia, canvas, staticCtx);
root.forEach((node) => {
draw(ctx, node);
});
const picture = rec.finishRecordingAsPicture();
const end = performance.now();
console.log("Recording time: ", end - start);
SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
};

export class Container {
public _root: Node[] = [];
private _root: Node[] = [];
private _staticCtx: StaticContext | null = null;
public unmounted = false;

private values = new Set<SharedValue<unknown>>();
Expand All @@ -47,13 +56,14 @@ export class Container {
if (this.mapperId !== null) {
Rea.stopMapper(this.mapperId);
}
const { nativeId, Skia } = this;
const { nativeId, Skia, _staticCtx } = this;
this.mapperId = Rea.startMapper(() => {
"worklet";
drawOnscreen(Skia, nativeId, root);
drawOnscreen(Skia, nativeId, root, _staticCtx!);
}, Array.from(this.values));
}
this._root = root;
this._staticCtx = createStaticContext(this.Skia);
}

clear() {
Expand All @@ -66,9 +76,9 @@ export class Container {
throw new Error("React Native Skia only supports Reanimated 3 and above");
}
if (isOnscreen) {
const { nativeId, Skia, root } = this;
const { nativeId, Skia, root, _staticCtx } = this;
Rea.runOnUI(() => {
drawOnscreen(Skia, nativeId, root);
drawOnscreen(Skia, nativeId, root, _staticCtx!);
})();
}
}
Expand All @@ -94,7 +104,7 @@ export class Container {
}

drawOnCanvas(canvas: SkCanvas) {
const ctx = createDrawingContext(this.Skia, canvas);
const ctx = createDrawingContext(this.Skia, canvas, this._staticCtx!);
this.root.forEach((node) => {
draw(ctx, node);
});
Expand Down
52 changes: 32 additions & 20 deletions packages/skia/src/sksg/DrawingContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import type {
SkPaint,
} from "../skia/types";

import type { DeclarationContext } from "./DeclarationContext";
import { createDeclarationContext } from "./DeclarationContext";
import type { StaticContext } from "./StaticContext";

const computeClip = (
Skia: Skia,
Expand Down Expand Up @@ -61,32 +62,36 @@ const processColor = (
}
};

export const createDrawingContext = (Skia: Skia, canvas: SkCanvas) => {
export const createDrawingContext = (
Skia: Skia,
canvas: SkCanvas,
staticCtx: StaticContext
) => {
"worklet";
const state = {
staticCtx,
declCtx: createDeclarationContext(Skia),
paints: [Skia.Paint()],
};

const getPaint = () => {
return state.paints[state.paints.length - 1];
};

const processPaint = (
{
opacity,
color,
strokeWidth,
blendMode,
style,
strokeJoin,
strokeCap,
strokeMiter,
antiAlias,
dither,
paint: paintProp,
}: DrawingNodeProps,
declCtx: DeclarationContext
) => {
const processPaint = ({
opacity,
color,
strokeWidth,
blendMode,
style,
strokeJoin,
strokeCap,
strokeMiter,
antiAlias,
dither,
paint: paintProp,
}: DrawingNodeProps) => {
const { declCtx } = state;
if (paintProp) {
declCtx.paints.push(paintProp);
return true;
Expand Down Expand Up @@ -116,7 +121,14 @@ export const createDrawingContext = (Skia: Skia, canvas: SkCanvas) => {
pathEffect !== undefined
) {
if (!shouldRestore) {
state.paints.push(getPaint().copy());
const i = state.paints.length;
if (!state.staticCtx.paints[i]) {
state.staticCtx.paints.push(Skia.Paint());
}
const paint = state.staticCtx.paints[i];
const parentPaint = getPaint();
paint.assign(parentPaint);
state.paints.push(paint);
shouldRestore = true;
}
}
Expand Down Expand Up @@ -215,11 +227,11 @@ export const createDrawingContext = (Skia: Skia, canvas: SkCanvas) => {
return {
Skia,
canvas,
save: () => state.paints.push(getPaint().copy()),
restore: () => state.paints.pop(),
getPaint,
processPaint,
processMatrixAndClipping,
declCtx: state.declCtx,
};
};

Expand Down
9 changes: 9 additions & 0 deletions packages/skia/src/sksg/StaticContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Skia, SkPaint } from "../skia/types";

export interface StaticContext {
paints: SkPaint[];
}

export const createStaticContext = (Skia: Skia) => {
return { paints: [Skia.Paint()] };
};
16 changes: 16 additions & 0 deletions packages/skia/src/sksg/nodes/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,19 @@ export interface Node<Props = unknown> {
props: Props;
children: Node[];
}

export const sortNodes = (children: Node[]) => {
"worklet";
const declarations: Node[] = [];
const drawings: Node[] = [];

children.forEach((node) => {
if (node.isDeclaration) {
declarations.push(node);
} else {
drawings.push(node);
}
});

return { declarations, drawings };
};
25 changes: 11 additions & 14 deletions packages/skia/src/sksg/nodes/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type DeclarationContext,
} from "../DeclarationContext";

import type { Node } from "./Node";
import { sortNodes, type Node } from "./Node";
import {
drawAtlas,
drawBox,
Expand Down Expand Up @@ -248,21 +248,18 @@ function processDeclarations(ctx: DeclarationContext, node: Node<any>) {
const preProcessContext = (
ctx: DrawingContext,
props: DrawingNodeProps,
node: Node<any>
declarationChildren: Node<any>[]
) => {
"worklet";
const shouldRestoreMatrix = ctx.processMatrixAndClipping(props, props.layer);
const declCtx = createDeclarationContext(ctx.Skia);
node.children.forEach((child) => {
if (child.isDeclaration) {
processDeclarations(declCtx, child);
}
declarationChildren.forEach((child) => {
processDeclarations(ctx.declCtx, child);
});
const shouldRestorePaint = ctx.processPaint(props, declCtx);
const shouldRestorePaint = ctx.processPaint(props);
return {
shouldRestoreMatrix,
shouldRestorePaint,
extraPaints: declCtx.paints.popAll(),
extraPaints: ctx.declCtx.paints.popAll(),
};
};

Expand Down Expand Up @@ -318,10 +315,12 @@ export function draw(ctx: DrawingContext, node: Node<any>) {
return;
}
const { type, props: rawProps, children } = node;

// Regular nodes
const props = materialize(rawProps);
const { declarations, drawings } = sortNodes(children);
const { shouldRestoreMatrix, shouldRestorePaint, extraPaints } =
preProcessContext(ctx, props, node);
preProcessContext(ctx, props, declarations);
const paints = [ctx.getPaint(), ...extraPaints];
paints.forEach((paint) => {
const lctx = { paint, Skia: ctx.Skia, canvas: ctx.canvas };
Expand Down Expand Up @@ -398,10 +397,8 @@ export function draw(ctx: DrawingContext, node: Node<any>) {
}
}
});
children.forEach((child) => {
if (!child.isDeclaration) {
draw(ctx, child);
}
drawings.forEach((child) => {
draw(ctx, child);
});
if (shouldRestoreMatrix) {
ctx.canvas.restore();
Expand Down
Loading