diff --git a/apps/paper/src/Examples/Matrix/Matrix.tsx b/apps/paper/src/Examples/Matrix/Matrix.tsx
index 2302e0e92d..423585991d 100644
--- a/apps/paper/src/Examples/Matrix/Matrix.tsx
+++ b/apps/paper/src/Examples/Matrix/Matrix.tsx
@@ -1,6 +1,7 @@
import {
BlurMask,
Canvas,
+ Canvas2,
Fill,
Group,
useClock,
@@ -41,7 +42,7 @@ export const Matrix = () => {
}
const symbols = font.getGlyphIDs("abcdefghijklmnopqrstuvwxyz");
return (
-
+
);
};
diff --git a/apps/paper/src/Examples/Matrix/Symbol.tsx b/apps/paper/src/Examples/Matrix/Symbol.tsx
index 5c038311a0..215d0a2107 100644
--- a/apps/paper/src/Examples/Matrix/Symbol.tsx
+++ b/apps/paper/src/Examples/Matrix/Symbol.tsx
@@ -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 {
@@ -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]);
diff --git a/packages/skia/cpp/api/JsiSkPaint.h b/packages/skia/cpp/api/JsiSkPaint.h
index fce2faf2fd..e7b1e4839f 100644
--- a/packages/skia/cpp/api/JsiSkPaint.h
+++ b/packages/skia/cpp/api/JsiSkPaint.h
@@ -27,6 +27,12 @@ class JsiSkPaint : public JsiSkWrappingSharedPtrHostObject {
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(
@@ -163,7 +169,8 @@ class JsiSkPaint : public JsiSkWrappingSharedPtrHostObject {
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),
diff --git a/packages/skia/src/skia/types/Paint/Paint.ts b/packages/skia/src/skia/types/Paint/Paint.ts
index 0216a1458f..007a4ecedf 100644
--- a/packages/skia/src/skia/types/Paint/Paint.ts
+++ b/packages/skia/src/skia/types/Paint/Paint.ts
@@ -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).
diff --git a/packages/skia/src/skia/web/JsiSkPaint.ts b/packages/skia/src/skia/web/JsiSkPaint.ts
index 029ace1cfc..4254b93a56 100644
--- a/packages/skia/src/skia/web/JsiSkPaint.ts
+++ b/packages/skia/src/skia/web/JsiSkPaint.ts
@@ -34,6 +34,10 @@ export class JsiSkPaint extends HostObject 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();
}
diff --git a/packages/skia/src/sksg/Container.ts b/packages/skia/src/sksg/Container.ts
index 20a78e2c27..4fd4211d68 100644
--- a/packages/skia/src/sksg/Container.ts
+++ b/packages/skia/src/sksg/Container.ts
@@ -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>();
@@ -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() {
@@ -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!);
})();
}
}
@@ -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);
});
diff --git a/packages/skia/src/sksg/DrawingContext.ts b/packages/skia/src/sksg/DrawingContext.ts
index 677cd0150b..d27532b790 100644
--- a/packages/skia/src/sksg/DrawingContext.ts
+++ b/packages/skia/src/sksg/DrawingContext.ts
@@ -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,
@@ -61,9 +62,15 @@ 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()],
};
@@ -71,22 +78,20 @@ export const createDrawingContext = (Skia: Skia, canvas: SkCanvas) => {
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;
@@ -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;
}
}
@@ -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,
};
};
diff --git a/packages/skia/src/sksg/StaticContext.ts b/packages/skia/src/sksg/StaticContext.ts
new file mode 100644
index 0000000000..06539bf945
--- /dev/null
+++ b/packages/skia/src/sksg/StaticContext.ts
@@ -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()] };
+};
diff --git a/packages/skia/src/sksg/nodes/Node.ts b/packages/skia/src/sksg/nodes/Node.ts
index 7cd9cba646..b247e99240 100644
--- a/packages/skia/src/sksg/nodes/Node.ts
+++ b/packages/skia/src/sksg/nodes/Node.ts
@@ -6,3 +6,19 @@ export interface Node {
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 };
+};
diff --git a/packages/skia/src/sksg/nodes/context.ts b/packages/skia/src/sksg/nodes/context.ts
index bf6ffdbb11..0961aaed90 100644
--- a/packages/skia/src/sksg/nodes/context.ts
+++ b/packages/skia/src/sksg/nodes/context.ts
@@ -8,7 +8,7 @@ import {
type DeclarationContext,
} from "../DeclarationContext";
-import type { Node } from "./Node";
+import { sortNodes, type Node } from "./Node";
import {
drawAtlas,
drawBox,
@@ -248,21 +248,18 @@ function processDeclarations(ctx: DeclarationContext, node: Node) {
const preProcessContext = (
ctx: DrawingContext,
props: DrawingNodeProps,
- node: Node
+ declarationChildren: Node[]
) => {
"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(),
};
};
@@ -318,10 +315,12 @@ export function draw(ctx: DrawingContext, node: Node) {
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 };
@@ -398,10 +397,8 @@ export function draw(ctx: DrawingContext, node: Node) {
}
}
});
- children.forEach((child) => {
- if (!child.isDeclaration) {
- draw(ctx, child);
- }
+ drawings.forEach((child) => {
+ draw(ctx, child);
});
if (shouldRestoreMatrix) {
ctx.canvas.restore();