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

New scene graph #2839

Merged
merged 87 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
6a30551
:wrench:
wcandillon Dec 19, 2024
59fadf1
:wrench:
wcandillon Dec 19, 2024
92710f1
:wrench:
wcandillon Dec 19, 2024
1a92f9f
:wrench:
wcandillon Dec 19, 2024
45945f2
:wrench:
wcandillon Dec 19, 2024
7250db8
:wrench:
wcandillon Dec 19, 2024
b0f2919
:wrench:
wcandillon Dec 19, 2024
6bae1c9
:wrench:
wcandillon Dec 19, 2024
1c25acb
:wrench:
wcandillon Dec 19, 2024
870ebc5
:wrench:
wcandillon Dec 19, 2024
b30f614
:wrench:
wcandillon Dec 19, 2024
27bac61
:wrench:
wcandillon Dec 21, 2024
222df9b
:wrench:
wcandillon Dec 23, 2024
1360484
:wrench:
wcandillon Dec 24, 2024
c2d376e
:wrench:
wcandillon Dec 24, 2024
44463b6
:wrench:
wcandillon Dec 24, 2024
02318d2
:wrench:
wcandillon Dec 24, 2024
aa8df67
:wrench:
wcandillon Dec 24, 2024
cb16ba5
:wrench:
wcandillon Dec 24, 2024
1961048
:wrench:
wcandillon Dec 24, 2024
c63693f
:wrench:
wcandillon Dec 24, 2024
251a78b
:wrench:
wcandillon Dec 24, 2024
dec4eb1
:wrench:
wcandillon Dec 24, 2024
1e140ab
:wrench:
wcandillon Dec 24, 2024
872ddb3
:wrench:
wcandillon Dec 24, 2024
215b68d
:wrench:
wcandillon Dec 24, 2024
1ce3d42
:wrench:
wcandillon Dec 24, 2024
f54a4f9
:wrench:
wcandillon Dec 24, 2024
74f640e
:wrench:
wcandillon Dec 24, 2024
6db50c6
:wrench:
wcandillon Dec 24, 2024
d320f2c
:wrench:
wcandillon Dec 24, 2024
a00a7b8
:wrench:
wcandillon Dec 24, 2024
a44962c
:wrench:
wcandillon Dec 25, 2024
f48de17
:green_heart:
wcandillon Dec 25, 2024
d00eb76
:wrench:
wcandillon Dec 25, 2024
3da02de
:wrench:
wcandillon Dec 25, 2024
163571f
:wrench:
wcandillon Dec 25, 2024
203ccc9
:wrench:
wcandillon Dec 25, 2024
d9e11c8
:wrench:
wcandillon Dec 25, 2024
8b4f95d
:wrench:
wcandillon Dec 25, 2024
8a1d613
:wrench:
wcandillon Dec 25, 2024
e52e0a4
:wrench:
wcandillon Dec 25, 2024
da38bbd
:wrench:
wcandillon Dec 25, 2024
87ab7ae
:wrench:
wcandillon Dec 25, 2024
f46afd7
:wrench:
wcandillon Dec 25, 2024
e1f35a1
:wrench:
wcandillon Dec 26, 2024
618cc5b
:wrench:
wcandillon Dec 27, 2024
b90fef6
:wrench:
wcandillon Dec 27, 2024
12b56b0
:wrench:
wcandillon Dec 27, 2024
435510b
:wrench:
wcandillon Dec 27, 2024
90372f5
:wrench:
wcandillon Dec 27, 2024
edbf149
:wrench:
wcandillon Dec 27, 2024
4e605aa
:wrench:
wcandillon Dec 27, 2024
dd1bb1a
:wrench:
wcandillon Dec 27, 2024
8084980
:wrench:
wcandillon Dec 27, 2024
0629108
:wrench:
wcandillon Dec 27, 2024
dc76f5e
:wrench:
wcandillon Dec 27, 2024
9676bd2
:wrench:
wcandillon Dec 28, 2024
a90b676
:wrench:
wcandillon Dec 28, 2024
2ca0d15
:wrench:
wcandillon Dec 28, 2024
06b8dda
:wrench:
wcandillon Dec 28, 2024
ea43fa1
:wrench:
wcandillon Dec 28, 2024
1d89250
:wrench:
wcandillon Dec 28, 2024
c49837a
:wrench:
wcandillon Dec 28, 2024
0a1ff7b
:wrench:
wcandillon Dec 28, 2024
e92ca09
:wrench:
wcandillon Dec 28, 2024
8317f18
:wrench:
wcandillon Dec 28, 2024
0064770
:wrench:
wcandillon Dec 29, 2024
83376c7
:wrench:
wcandillon Dec 29, 2024
26407fd
:wrench:
wcandillon Dec 29, 2024
c7ac9f3
:wrench:
wcandillon Dec 29, 2024
f9bf3ce
:wrench:
wcandillon Dec 29, 2024
8878a94
:wrench:
wcandillon Dec 29, 2024
e5e2e3e
Merge branch 'main' into sksg2
wcandillon Dec 29, 2024
f77d8af
Delete packages/skia/src/views/SkiaSGView.tsx
wcandillon Dec 29, 2024
ab6d8fb
Merge branch 'main' into sksg2
wcandillon Dec 29, 2024
732c9a0
Merge branch 'main' into sksg2
wcandillon Dec 29, 2024
e81164c
Merge branch 'main' into sksg2
wcandillon Dec 30, 2024
fa8b1c1
Merge branch 'main' into sksg2
wcandillon Dec 30, 2024
057d779
Remove bogus change
wcandillon Dec 30, 2024
c7c3ec5
Remove bogus change
wcandillon Dec 30, 2024
69c19df
remove bogus change
wcandillon Dec 30, 2024
37729d8
Merge branch 'main' into sksg2
wcandillon Dec 30, 2024
afd4dbf
disable new canvas for now
wcandillon Dec 30, 2024
49155a1
:green_heart:
wcandillon Dec 30, 2024
c817cf8
remove bogus log
wcandillon Dec 30, 2024
9d35c85
Merge branch 'main' into sksg2
wcandillon Dec 30, 2024
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
1 change: 0 additions & 1 deletion apps/paper/src/Tests/deserialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ const parseProp = (value: any, assets: Assets): any => {
Object.keys(value).forEach((key) => {
parsed[key] = parseProp(value[key], assets);
});
console.log({ parsed });
return parsed;
}
return value;
Expand Down
2 changes: 1 addition & 1 deletion packages/skia/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
// not be probed for tests
modulePathIgnorePatterns: [
"<rootDir>/lib",
"(setup)|(setup.(ts|tsx))$|globalSetup.ts|globalTeardown.ts",
"(setup)|(setup.(ts|tsx))$|globalSetup.ts|globalTeardown.ts|MockDeclaration.ts",
],
transform: {
"^.+\\.(ts|tsx)$": ["ts-jest", { tsconfig: "./tsconfig.test.json" }],
Expand Down
2 changes: 1 addition & 1 deletion packages/skia/src/__tests__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const checkImage = (
if (diffPixelsCount > maxPixelDiff && !shouldFail) {
console.log(`${p} didn't match`);
fs.writeFileSync(`${p}.test.png`, PNG.sync.write(toTest));
fs.writeFileSync(`${p}-diff-test.png`, PNG.sync.write(diffImage));
//fs.writeFileSync(`${p}-diff-test.png`, PNG.sync.write(diffImage));
}
if (shouldFail) {
expect(diffPixelsCount).not.toBeLessThanOrEqual(maxPixelDiff);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions packages/skia/src/dom/nodes/datatypes/Circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import type { CircleDef, ScalarCircleDef } from "../../types";

export const isCircleScalarDef = (def: CircleDef): def is ScalarCircleDef =>
export const isCircleScalarDef = (def: CircleDef): def is ScalarCircleDef => {
// We have an issue to check property existence on JSI backed instances
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(def as any).cx !== undefined;
return (def as any).cx !== undefined;
};

export const processCircle = (def: CircleDef) => {
if (isCircleScalarDef(def)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/skia/src/dom/nodes/datatypes/Path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export const processPath = (Skia: Skia, rawPath: PathDef) => {
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isPathDef = (def: any): def is PathDef =>
typeof def === "string" || isPath(def);
export const isPathDef = (def: any): def is PathDef => {
return typeof def === "string" || isPath(def);
};
11 changes: 6 additions & 5 deletions packages/skia/src/dom/nodes/datatypes/Rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ import type { RectCtor, RectDef, RRectCtor, RRectDef } from "../../types";
import { processRadius } from "./Radius";

export const isEdge = (pos: Vector, b: SkRect) => {
"worklet";
return (
pos.x === b.x || pos.y === b.y || pos.x === b.width || pos.y === b.height
);
};

// We have an issue to check property existence on JSI backed instances
const isRRectCtor = (def: RRectDef): def is RRectCtor =>
(def as any).rect === undefined;
const isRRectCtor = (def: RRectDef): def is RRectCtor => {
return (def as any).rect === undefined;
};
// We have an issue to check property existence on JSI backed instances
const isRectCtor = (def: RectDef): def is RectCtor =>
(def as any).rect === undefined;
const isRectCtor = (def: RectDef): def is RectCtor => {
return (def as any).rect === undefined;
};

export const processRect = (Skia: Skia, def: RectDef) => {
if (isRectCtor(def)) {
Expand Down
14 changes: 12 additions & 2 deletions packages/skia/src/dom/types/DeclarationContext.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"worklet";

import type {
SkShader,
SkPaint,
Expand Down Expand Up @@ -25,8 +27,11 @@ export const composeDeclarations = <T>(filters: T[], composer: Composer<T>) => {
class Declaration<T> {
private decls: T[] = [];
private indexes = [0];
private composer?: Composer<T>;

constructor(private composer?: Composer<T>) {}
constructor(composer?: Composer<T>) {
this.composer = composer;
}

private get index() {
return this.indexes[this.indexes.length - 1];
Expand All @@ -53,6 +58,9 @@ class Declaration<T> {
}

popAllAsOne() {
if (this.decls.length === 0) {
return undefined;
}
if (!this.composer) {
throw new Error("No composer for this type of declaration");
}
Expand All @@ -62,14 +70,16 @@ class Declaration<T> {
}

export class DeclarationContext {
public Skia: Skia;
readonly paints: Declaration<SkPaint>;
readonly maskFilters: Declaration<SkMaskFilter>;
readonly shaders: Declaration<SkShader>;
readonly pathEffects: Declaration<SkPathEffect>;
readonly imageFilters: Declaration<SkImageFilter>;
readonly colorFilters: Declaration<SkColorFilter>;

constructor(private Skia: Skia) {
constructor(Skia: Skia) {
this.Skia = Skia;
const peComp = this.Skia.PathEffect.MakeCompose.bind(this.Skia.PathEffect);
const ifComp = this.Skia.ImageFilter.MakeCompose.bind(
this.Skia.ImageFilter
Expand Down
4 changes: 2 additions & 2 deletions packages/skia/src/external/reanimated/renderHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import type { Node } from "../../dom/types";

import Rea from "./ReanimatedProxy";

let HAS_REANIMATED = false;
let HAS_REANIMATED_3 = false;
export let HAS_REANIMATED = false;
export let HAS_REANIMATED_3 = false;
try {
require("react-native-reanimated");
HAS_REANIMATED = true;
Expand Down
2 changes: 1 addition & 1 deletion packages/skia/src/renderer/HostConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export const skHostConfig: SkiaHostConfig = {
/**
* This function is used by the reconciler in order to calculate current time for prioritising work.
*/
now: Date.now,
supportsMutation: true,
isPrimaryRenderer: false,
supportsPersistence: false,
Expand Down Expand Up @@ -242,6 +241,7 @@ export const skHostConfig: SkiaHostConfig = {
insertBefore: (parent, child, before) => {
insertBefore(parent, child, before);
},

// see https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
getCurrentEventPriority: () => DefaultEventPriority,
beforeActiveInstanceBlur: () => {},
Expand Down
24 changes: 9 additions & 15 deletions packages/skia/src/renderer/__tests__/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ import type { Server, WebSocket } from "ws";

import type * as SkiaExports from "../../index";
import { JsiSkApi } from "../../skia/web/JsiSkia";
import type { Node } from "../../dom/nodes";
import { JsiSkDOM } from "../../dom/nodes";
import { Group } from "../components";
import type { SkImage, SkFont, Skia, SkCanvas } from "../../skia/types";
import { isPath } from "../../skia/types";
import { E2E } from "../../__tests__/setup";
import { SkiaRoot } from "../Reconciler";
import { JsiDrawingContext } from "../../dom/types/DrawingContext";
import { LoadSkiaWeb } from "../../web/LoadSkiaWeb";
import { SkiaSGRoot } from "../../sksg/Reconciler";
import type { Node } from "../../sksg/nodes";

import { SkiaObject } from "./e2e/setup";

Expand Down Expand Up @@ -172,11 +170,6 @@ export const importSkia = (): typeof SkiaExports => {
};
};

export const getSkDOM = () => {
const { Skia } = importSkia();
return new JsiSkDOM({ Skia }, false);
};

export const PIXEL_RATIO = 3;
export const fontSize = 32 * PIXEL_RATIO;
export const width = 256 * PIXEL_RATIO;
Expand All @@ -197,21 +190,20 @@ export const mountCanvas = (element: ReactNode) => {
expect(ckSurface).toBeDefined();
const canvas = ckSurface.getCanvas();

const root = new SkiaRoot(Skia, false);
const root = new SkiaSGRoot(Skia);
root.render(element);
return {
surface: ckSurface,
root,
draw: () => {
const ctx = new JsiDrawingContext(Skia, canvas);
root.dom.render(ctx);
root.drawOnCanvas(canvas);
},
};
};

export const serialize = (element: ReactNode) => {
const { root } = mountCanvas(element);
const serialized = serializeNode(root.dom);
const serialized = serializeNode(root.sg);
return JSON.stringify(serialized);
};

Expand All @@ -228,6 +220,8 @@ interface SerializedNode {
const serializeSkOjects = (obj: any): any => {
if (typeof obj === "function") {
return { __typename__: "Function", source: `${obj.toString()}` };
} else if (obj instanceof Float32Array) {
return { __typename__: "Float32Array", value: Array.from(obj) };
} else if (Array.isArray(obj)) {
return obj.map((item) => serializeSkOjects(item));
} else if (obj && typeof obj === "object" && "__typename__" in obj) {
Expand Down Expand Up @@ -316,7 +310,7 @@ const serializeSkOjects = (obj: any): any => {

const serializeNode = (node: Node<any>): SerializedNode => {
const props: any = {};
const ogProps = node.getProps();
const ogProps = node.props;
if (ogProps) {
Object.keys(ogProps)
.filter((key) => key !== "children")
Expand All @@ -327,7 +321,7 @@ const serializeNode = (node: Node<any>): SerializedNode => {
return {
type: node.type,
props,
children: node.children().map((child) => serializeNode(child)),
children: node.children.map((child) => serializeNode(child)),
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
export type SharedValueType<T = number> = {
value: T;
};

export type AnimatedProp<T> = T | SharedValueType<T>;
export type AnimatedProp<T> = T | { value: T };

export type AnimatedProps<T, O extends keyof T | never = never> = {
[K in keyof T]: K extends "children"
Expand Down
6 changes: 4 additions & 2 deletions packages/skia/src/renderer/typeddash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const mapKeys = <T extends object>(obj: T) =>
Object.keys(obj) as (keyof T)[];
export const mapKeys = <T extends object>(obj: T) => {
"worklet";
return Object.keys(obj) as (keyof T)[];
};

export const exhaustiveCheck = (a: never): never => {
"worklet";
Expand Down
6 changes: 4 additions & 2 deletions packages/skia/src/skia/types/Path/Path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ export enum PathVerb {

export type PathCommand = number[];

export const isPath = (obj: SkJSIInstance<string> | null): obj is SkPath =>
obj !== null && obj.__typename__ === "Path";
export const isPath = (obj: SkJSIInstance<string> | null): obj is SkPath => {
"worklet";
return obj !== null && obj.__typename__ === "Path";
};

export interface SkPath extends SkJSIInstance<"Path"> {
/**
Expand Down
11 changes: 7 additions & 4 deletions packages/skia/src/skia/types/Shader/Shader.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"worklet";

import type { SkJSIInstance } from "../JsiInstance";
import type { Vector } from "../Point";
import type { SkRuntimeEffect, SkRuntimeShaderBuilder } from "../RuntimeEffect";
Expand All @@ -18,12 +20,13 @@ export interface Uniforms {
[name: string]: Uniform;
}

const isVector = (obj: unknown): obj is Vector =>
const isVector = (obj: unknown): obj is Vector => {
// We have an issue to check property existence on JSI backed instances
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(obj as any).x !== undefined && (obj as any).y !== undefined;
return (obj as any).x !== undefined && (obj as any).y !== undefined;
};

const processValue = (values: number[], value: Uniform) => {
function processValue(values: number[], value: Uniform) {
if (typeof value === "number") {
values.push(value);
} else if (Array.isArray(value)) {
Expand All @@ -33,7 +36,7 @@ const processValue = (values: number[], value: Uniform) => {
} else if (value instanceof Float32Array) {
values.push(...value);
}
};
}

export const processUniforms = (
source: SkRuntimeEffect,
Expand Down
102 changes: 102 additions & 0 deletions packages/skia/src/sksg/Container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { type SharedValue } from "react-native-reanimated";

import Rea from "../external/reanimated/ReanimatedProxy";
import type { Skia, SkCanvas } from "../skia/types";
import {
HAS_REANIMATED,
HAS_REANIMATED_3,
} from "../external/reanimated/renderHelpers";

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

const drawOnscreen = (Skia: Skia, nativeId: number, root: Node[]) => {
"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 = new DrawingContext(Skia, canvas);
root.forEach((node) => {
draw(ctx, node);
});
const picture = rec.finishRecordingAsPicture();
SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
};

export class Container {
public _root: Node[] = [];
public unmounted = false;

private values = new Set<SharedValue<unknown>>();
private mapperId: number | null = null;

constructor(public Skia: Skia, private nativeId: number) {}

get root() {
return this._root;
}

set root(root: Node[]) {
const isOnscreen = this.nativeId !== -1;
if (HAS_REANIMATED && !HAS_REANIMATED_3) {
throw new Error("React Native Skia only supports Reanimated 3 and above");
}
if (isOnscreen) {
if (this.mapperId !== null) {
Rea.stopMapper(this.mapperId);
}
const { nativeId, Skia } = this;
this.mapperId = Rea.startMapper(() => {
"worklet";
drawOnscreen(Skia, nativeId, root);
}, Array.from(this.values));
}
this._root = root;
}

clear() {
console.log("clear container");
}

redraw() {
const isOnscreen = this.nativeId !== -1;
if (HAS_REANIMATED && !HAS_REANIMATED_3) {
throw new Error("React Native Skia only supports Reanimated 3 and above");
}
if (isOnscreen) {
const { nativeId, Skia, root } = this;
Rea.runOnUI(() => {
drawOnscreen(Skia, nativeId, root);
})();
}
}

getNativeId() {
return this.nativeId;
}

unregisterValues(values: object) {
Object.values(values)
.filter(isSharedValue)
.forEach((value) => {
this.values.delete(value);
});
}

registerValues(values: object) {
Object.values(values)
.filter(isSharedValue)
.forEach((value) => {
this.values.add(value);
});
}

drawOnCanvas(canvas: SkCanvas) {
const ctx = new DrawingContext(this.Skia, canvas);
this.root.forEach((node) => {
draw(ctx, node);
});
}
}
Loading
Loading