Skip to content

Commit

Permalink
feat(πŸ‘¨πŸ»β€πŸŽ¨): Opaque property (Shopify#2776)
Browse files Browse the repository at this point in the history
The opaque property enables the use of SurfaceView on Android instead of TextureView
  • Loading branch information
wcandillon authored Nov 29, 2024
1 parent 06ecae8 commit 88dec7f
Show file tree
Hide file tree
Showing 43 changed files with 488 additions and 162 deletions.
2 changes: 1 addition & 1 deletion apps/docs/docs/canvas/canvas.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Behind the scenes, it is using its own React renderer.
|:-----|:---------|:-----------------|
| style? | `ViewStyle` | View style |
| ref? | `Ref<SkiaView>` | Reference to the `SkiaView` object |
| mode? | `default` or `continuous` | By default, the canvas is only updated when the drawing tree or animation values change. With `mode="continuous"`, the canvas will redraw on every frame |
| opaque? | `boolean` | By default, the canvas is transparent but on Android, you can make it opaque to improve performance. |
| onSize? | `SharedValue<Size>` | Reanimated value to which the canvas size will be assigned (see [canvas size](/docs/animations/hooks#canvas-size)) |
| onLayout? | `NativeEvent<LayoutEvent>` | Invoked on mount and on layout changes (see [onLayout](https://reactnative.dev/docs/view#onlayout)) |

Expand Down
10 changes: 5 additions & 5 deletions apps/paper/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-wgpu (0.1.19):
- react-native-wgpu (0.1.20):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1650,7 +1650,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.34.0):
- RNScreens (4.3.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1937,7 +1937,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: ab8f4a3d8180913bd78ae75dd599c94cce3d5e9a
react-native-skia: 1549ee5068efc5a004b84b2e0ba109c6234e2fde
react-native-slider: 97ce0bd921f40de79cead9754546d5e4e7ba44f8
react-native-wgpu: 8d0437a304318e0e3d6ccbfed2a39880f8eae4dd
react-native-wgpu: efaa8c7c3ae15b346d887d13cca2fe72ed5ea105
React-nativeconfig: 57781b79e11d5af7573e6f77cbf1143b71802a6d
React-NativeModulesApple: 7ff2e2cfb2e5fa5bdedcecf28ce37e696c6ef1e1
React-perflogger: 8a360ccf603de6ddbe9ff8f54383146d26e6c936
Expand Down Expand Up @@ -1966,10 +1966,10 @@ SPEC CHECKSUMS:
ReactCommon: 6ef348087d250257c44c0204461c03f036650e9b
RNGestureHandler: 939f21fabf5d45a725c0bf175eb819dd25cf2e70
RNReanimated: 190c12cb20dfa828353e99775beaa1bdf36e7ed9
RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06
RNScreens: b03d696c70cc5235ce4587fcc27ae1a93a48f98c
RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae
Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8

PODFILE CHECKSUM: debc09f5cfcbea21f946ca0be3faa5351e907125

Expand Down
3 changes: 3 additions & 0 deletions apps/paper/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { HeaderBackButtonProps } from "@react-navigation/elements";
import { HeaderBackButton } from "@react-navigation/elements";
import { FiberProvider } from "its-fine";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { enableScreens } from "react-native-screens";

import {
ReanimatedExample,
Expand Down Expand Up @@ -84,6 +85,8 @@ const HeaderLeft = (props: HeaderBackButtonProps) => {
);
};

enableScreens(true);

const App = () => {
const Stack = createNativeStackNavigator<StackParamList>();
const assets = useAssets();
Expand Down
2 changes: 1 addition & 1 deletion apps/paper/src/Examples/Breathe/Breathe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const Breathe = () => {

return (
<View style={{ flex: 1 }}>
<Canvas style={styles.container}>
<Canvas style={styles.container} opaque>
<Fill color="rgb(36,43,56)" />
<Group origin={center} transform={transform} blendMode="screen">
<BlurMask style="solid" blur={40} />
Expand Down
2 changes: 1 addition & 1 deletion apps/paper/src/Examples/Glassmorphism/Glassmorphism.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const Glassmorphism = () => {
);

return (
<Canvas style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }} opaque>
<Fill color="black" />
<Circle c={c} r={radius}>
<LinearGradient
Expand Down
2 changes: 1 addition & 1 deletion apps/paper/src/Examples/Matrix/Matrix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const Matrix = () => {
}
const symbols = font.getGlyphIDs("abcdefghijklmnopqrstuvwxyz");
return (
<Canvas style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }} opaque>
<Fill color="black" />
<Group>
<BlurMask blur={8} style="solid" />
Expand Down
4 changes: 2 additions & 2 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 = 15;
export const ROWS = 30;
export const COLS = 8;
export const ROWS = 15;
const pos = vec(0, 0);

interface SymbolProps {
Expand Down
2 changes: 1 addition & 1 deletion apps/paper/src/Examples/Severance/Severance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const Severance = () => {
return (
<View style={{ flex: 1 }}>
<GestureDetector gesture={gesture}>
<Canvas style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }} opaque>
<CRT>
<Group>
<Fill color={BG} />
Expand Down
2 changes: 1 addition & 1 deletion apps/paper/src/Examples/Stickers/Stickers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const Stickers = () => {
}
return (
<View>
<Canvas style={{ width, height }}>
<Canvas style={{ width, height }} opaque>
<Picture matrix={pictureMatrix} image={image} />
<HelloSticker matrix={helloMatrix} />
<LocationSticker font={font} matrix={locationMatrix} />
Expand Down
4 changes: 2 additions & 2 deletions apps/paper/src/Examples/Video/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Video = () => {
style={{ flex: 1 }}
onPress={() => (paused.value = !paused.value)}
>
<Canvas style={{ flex: 1 }}>
<Canvas style={{ flex: 1 }} opaque>
<Fill>
<ImageShader
image={currentFrame}
Expand All @@ -62,7 +62,7 @@ export const Video = () => {
/>
</Canvas>
</Pressable>
<View style={{ height: 200 }}>
<View style={{ height: 200, backgroundColor: "white" }}>
<Slider
style={{ width, height: 40 }}
minimumValue={0}
Expand Down
10 changes: 6 additions & 4 deletions packages/skia/android/cpp/jni/include/JniSkiaBaseView.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ class JniSkiaBaseView {
}

protected:
virtual void surfaceAvailable(jobject surface, int width, int height) {
_skiaAndroidView->surfaceAvailable(surface, width, height);
virtual void surfaceAvailable(jobject surface, int width, int height,
bool opaque) {
_skiaAndroidView->surfaceAvailable(surface, width, height, opaque);
}

virtual void surfaceSizeChanged(jobject surface, int width, int height) {
_skiaAndroidView->surfaceSizeChanged(surface, width, height);
virtual void surfaceSizeChanged(jobject surface, int width, int height,
bool opaque) {
_skiaAndroidView->surfaceSizeChanged(surface, width, height, opaque);
}

virtual void surfaceDestroyed() { _skiaAndroidView->surfaceDestroyed(); }
Expand Down
10 changes: 6 additions & 4 deletions packages/skia/android/cpp/jni/include/JniSkiaDomView.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ class JniSkiaDomView : public jni::HybridClass<JniSkiaDomView>,
}

protected:
void surfaceAvailable(jobject surface, int width, int height) override {
JniSkiaBaseView::surfaceAvailable(surface, width, height);
void surfaceAvailable(jobject surface, int width, int height,
bool opaque) override {
JniSkiaBaseView::surfaceAvailable(surface, width, height, opaque);
}

void surfaceSizeChanged(jobject surface, int width, int height) override {
JniSkiaBaseView::surfaceSizeChanged(surface, width, height);
void surfaceSizeChanged(jobject surface, int width, int height,
bool opaque) override {
JniSkiaBaseView::surfaceSizeChanged(surface, width, height, opaque);
}

void surfaceDestroyed() override { JniSkiaBaseView::surfaceDestroyed(); }
Expand Down
10 changes: 6 additions & 4 deletions packages/skia/android/cpp/jni/include/JniSkiaPictureView.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ class JniSkiaPictureView : public jni::HybridClass<JniSkiaPictureView>,
}

protected:
void surfaceAvailable(jobject surface, int width, int height) override {
JniSkiaBaseView::surfaceAvailable(surface, width, height);
void surfaceAvailable(jobject surface, int width, int height,
bool opaque) override {
JniSkiaBaseView::surfaceAvailable(surface, width, height, opaque);
}

void surfaceSizeChanged(jobject surface, int width, int height) override {
JniSkiaBaseView::surfaceSizeChanged(surface, width, height);
void surfaceSizeChanged(jobject surface, int width, int height,
bool opaque) override {
JniSkiaBaseView::surfaceSizeChanged(surface, width, height, opaque);
}

void surfaceDestroyed() override { JniSkiaBaseView::surfaceDestroyed(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <android/looper.h>
#include <unistd.h>
#include <queue>

class MainThreadDispatcher {
private:
Expand All @@ -27,6 +28,10 @@ class MainThreadDispatcher {
return instance;
}

bool isOnMainThread() {
return ALooper_forThread() == mainLooper;
}

void post(std::function<void()> task) {
// TODO: this is disabled for now but we can clean this up
// if (ALooper_forThread() == mainLooper) {
Expand Down
38 changes: 31 additions & 7 deletions packages/skia/android/cpp/rnskia-android/OpenGLContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,30 @@

namespace RNSkia {

class OpenGLSharedContext {
public:
OpenGLSharedContext(const OpenGLSharedContext &) = delete;
OpenGLSharedContext &operator=(const OpenGLSharedContext &) = delete;

static OpenGLSharedContext &getInstance() {
static OpenGLSharedContext instance;
return instance;
}

gl::Display* getDisplay() { return _glDisplay.get(); }
gl::Context* getContext() { return _glContext.get(); }

private:
std::unique_ptr<gl::Display> _glDisplay;
std::unique_ptr<gl::Context> _glContext;

OpenGLSharedContext() {
_glDisplay = std::make_unique<gl::Display>();
auto glConfig = _glDisplay->chooseConfig();
_glContext = _glDisplay->makeContext(glConfig, nullptr);
}
};

class OpenGLContext {
public:
friend class OpenGLWindowContext;
Expand Down Expand Up @@ -128,24 +152,24 @@ class OpenGLContext {
// TODO: remove width, height
std::unique_ptr<WindowContext> MakeWindow(ANativeWindow *window, int width,
int height) {
auto display = OpenGLSharedContext::getInstance().getDisplay();
return std::make_unique<OpenGLWindowContext>(
_directContext.get(), _glDisplay.get(), _glContext.get(), window);
_directContext.get(), display, _glContext.get(), window);
}

GrDirectContext *getDirectContext() { return _directContext.get(); }

private:
EGLConfig _glConfig;
std::unique_ptr<gl::Display> _glDisplay;
std::unique_ptr<gl::Context> _glContext;
std::unique_ptr<gl::Surface> _glSurface;
sk_sp<GrDirectContext> _directContext;

OpenGLContext() {
_glDisplay = std::make_unique<gl::Display>();
_glConfig = _glDisplay->chooseConfig();
_glContext = _glDisplay->makeContext(_glConfig, nullptr);
_glSurface = _glDisplay->makePixelBufferSurface(_glConfig, 1, 1);
auto display = OpenGLSharedContext::getInstance().getDisplay();
auto sharedContext = OpenGLSharedContext::getInstance().getContext();
auto glConfig = display->chooseConfig();
_glContext = display->makeContext(glConfig, sharedContext);
_glSurface = display->makePixelBufferSurface(glConfig, 1, 1);
_glContext->makeCurrent(_glSurface.get());
auto backendInterface = GrGLMakeNativeInterface();
_directContext = GrDirectContexts::MakeGL(backendInterface);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ sk_sp<SkSurface> OpenGLWindowContext::getSurface() {

void OpenGLWindowContext::present() {
_glContext->makeCurrent(_glSurface.get());
// TODO: is flushAndSubmit needed here?
_directContext->flushAndSubmit();
_glSurface->present();
}
Expand Down
19 changes: 10 additions & 9 deletions packages/skia/android/cpp/rnskia-android/RNSkAndroidView.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ namespace RNSkia {

class RNSkBaseAndroidView {
public:
virtual void surfaceAvailable(jobject surface, int width, int height) = 0;
virtual void surfaceAvailable(jobject surface, int width, int height,
bool opaque) = 0;

virtual void surfaceDestroyed() = 0;

virtual void surfaceSizeChanged(jobject surface, int width, int height) = 0;
virtual void surfaceSizeChanged(jobject surface, int width, int height,
bool opaque) = 0;

virtual float getPixelDensity() = 0;

Expand All @@ -34,12 +36,10 @@ class RNSkAndroidView : public T, public RNSkBaseAndroidView {
std::make_shared<RNSkOpenGLCanvasProvider>(
std::bind(&RNSkia::RNSkView::requestRedraw, this), context)) {}

void surfaceAvailable(jobject surface, int width, int height) override {
void surfaceAvailable(jobject surface, int width, int height,
bool opaque) override {
std::static_pointer_cast<RNSkOpenGLCanvasProvider>(T::getCanvasProvider())
->surfaceAvailable(surface, width, height);

// Try to render directly when the surface has been set so that
// we don't have to wait until the draw loop returns.
->surfaceAvailable(surface, width, height, opaque);
RNSkView::redraw();
}

Expand All @@ -48,9 +48,10 @@ class RNSkAndroidView : public T, public RNSkBaseAndroidView {
->surfaceDestroyed();
}

void surfaceSizeChanged(jobject surface, int width, int height) override {
void surfaceSizeChanged(jobject surface, int width, int height,
bool opaque) override {
std::static_pointer_cast<RNSkOpenGLCanvasProvider>(T::getCanvasProvider())
->surfaceSizeChanged(surface, width, height);
->surfaceSizeChanged(surface, width, height, opaque);
// This is only need for the first time to frame, this renderImmediate call
// will invoke updateTexImage for the previous frame
RNSkView::redraw();
Expand Down
Loading

0 comments on commit 88dec7f

Please sign in to comment.