Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/build-skia.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
if: startsWith(matrix.target, 'android')
uses: nttld/setup-ndk@afb4c9964b521afb97c864b7d40b11e6911bd410 # v1.5.0
with:
ndk-version: r22b
ndk-version: r27c

- name: Setup Ninja
uses: seanmiddleditch/gha-setup-ninja@master
Expand Down
94 changes: 94 additions & 0 deletions apps/docs/docs/canvas/rendering-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
id: rendering-modes
title: Rendering Modes
sidebar_label: Rendering Modes
slug: /canvas/rendering-modes
---

React Native Skia supports two rendering paradigms: **Retained Mode** and **Immediate Mode**. Understanding when to use each is key to building performant graphics applications.
The Retained Mode allows for extremely fast animation time with a virtually zero FFI-cost if the drawing list is updated at low frequency. The immediate mode allows for dynamic drawing list but has a higher FFI-cost to pay.
Since immediate mode uses the same `<Canvas>` element, you can seamlessly combine both rendering modes in a single scene.


## Retained Mode (Default)

In retained mode, you declare your scene as a tree of React components. React Native Skia converts this tree into a display list that is extremely efficient to animate with Reanimated.
This approach is extremely fast and is best suited for user-interfaces and interactive graphics where the structure doesn't change at animation time.

```tsx twoslash
import React, {useEffect} from "react";
import { Canvas, Circle, Group } from "@shopify/react-native-skia";
import { useSharedValue, withSpring, useDerivedValue } from "react-native-reanimated";

export const RetainedModeExample = () => {
const radius = useSharedValue(50);
useEffect(() => {
radius.value = withSpring(radius.value === 50 ? 100 : 50);
}, []);
return (
<Canvas style={{ flex: 1 }}>
<Group>
<Circle cx={128} cy={128} r={radius} color="cyan" />
</Group>
</Canvas>
);
};
```

## Immediate Mode

In immediate mode, you issue drawing commands directly to a canvas on every frame. This gives you complete control over what gets drawn and when, but requires you to manage the drawing logic yourself.

React Native Skia provides immediate mode through the [Picture API](/docs/shapes/pictures).
This mode is extremely well-suited for scenes where the number of drawing commands changes on every animation frame. This is often the case for games, generative art, and particle systems where the scene changes unpredictably on each animation frame.

```tsx twoslash
import { Canvas, Picture, Skia } from "@shopify/react-native-skia";
import { useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated";
import { useEffect } from "react";

const size = 256;

export const ImmediateModeExample = () => {
const progress = useSharedValue(0);
const recorder = Skia.PictureRecorder();
const paint = Skia.Paint();

useEffect(() => {
progress.value = withRepeat(withTiming(1, { duration: 2000 }), -1, true);
}, [progress]);

const picture = useDerivedValue(() => {
"worklet";
const canvas = recorder.beginRecording(Skia.XYWHRect(0, 0, size, size));

// Variable number of circles based on progress
const count = Math.floor(progress.value * 20);
for (let i = 0; i < count; i++) {
const r = (i + 1) * 6;
paint.setColor(Skia.Color(`rgba(0, 122, 255, ${(i + 1) / 20})`));
canvas.drawCircle(size / 2, size / 2, r, paint);
}

return recorder.finishRecordingAsPicture();
});

return (
<Canvas style={{ flex: 1 }}>
<Picture picture={picture} />
</Canvas>
);
};
```

## Choosing the Right Mode

Here is a small list of use-cases and which mode would be best for that scenario. Keep in mind that since these modes use the same `<Canvas>` element they can be nicely composed with each other. For instance a game where the scene is dynamic on every animation frame and some game UI elements are built in Retained Mode.

| Scenario | Recommended Mode | Why |
|:---------|:-----------------|:----|
| UI with animated properties | Retained | Zero FFI cost during animation |
| Data visualization | Retained | Structure usually fixed |
| Fixed number of sprites/tiles | Retained | With the [Atlas API](/docs/shapes/atlas), single draw call |
| Game with dynamic entities | Immediate | Entities created/destroyed |
| Procedural/generative art | Immediate | Dynamic drawing commands |
4 changes: 2 additions & 2 deletions apps/docs/docs/pictures.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ sidebar_label: Pictures
slug: /shapes/pictures
---

React Native Skia works in retained mode: every re-render, we create a display list with support for animation values.
This is great to animate property values. However, if you want to execute a variable number of drawing commands, this is where you need to use pictures.
React Native Skia works in [retained mode](/docs/canvas/rendering-modes): every re-render, we create a display list with support for animation values.
This is great for animating property values with near-zero performance cost. However, if you need to execute a **variable number of drawing commands** each frame, this is where you need to use the Picture API which works in [immediate mode](/docs/canvas/rendering-modes#immediate-mode) API.

A Picture contains a list of drawing operations to be drawn on a canvas.
The picture is immutable and cannot be edited or changed after it has been created. It can be used multiple times in any canvas.
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/docs/shapes/atlas.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ sidebar_label: Atlas
slug: /shapes/atlas
---

The Atlas component is used for efficient rendering of multiple instances of the same texture or image. It is especially useful for drawing a very large number of similar objects, like sprites, with varying transformations.
The Atlas component is used for efficient rendering of multiple instances of the same texture or image. It is especially useful for drawing a very large number of similar objects, like sprites or tiles, with varying transformations.

Its design particularly useful when using with [Reanimated](#animations).
Atlas transforms can be animated with near-zero cost using worklets. This makes it ideal for tile-based maps, sprite animations, and any scenario where you have many instances of similar textures. Its design is particularly useful when combined with [Reanimated](#animations).

| Name | Type | Description |
|:--------|:-----------------|:-----------------|
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const sidebars = {
collapsed: true,
type: "category",
label: "Canvas",
items: ["canvas/canvas", "canvas/contexts"],
items: ["canvas/canvas", "canvas/rendering-modes", "canvas/contexts"],
},
{
collapsed: true,
Expand Down
2 changes: 1 addition & 1 deletion packages/skia/cpp/api/JsiSkPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ class JsiSkPath : public JsiSkWrappingSharedPtrHostObject<SkPath> {
auto x2 = arguments[2].asNumber();
auto y2 = arguments[3].asNumber();
getObject()->quadTo(x1, y1, x2, y2);
return jsi::Value::undefined();
return thisValue.getObject(runtime);
}

JSI_HOST_FUNCTION(rQuadTo) {
Expand Down
Loading
Loading