Skip to content

Commit

Permalink
Merge branch 'main' into terrysahaidak/upload-texture
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Sep 29, 2024
2 parents 3eb7f24 + 5c58cae commit 970fe0d
Show file tree
Hide file tree
Showing 25 changed files with 509 additions and 1 deletion.
25 changes: 25 additions & 0 deletions apps/paper/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,27 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-wgpu (0.1.7):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- React-nativeconfig (0.75.2)
- React-NativeModulesApple (0.75.2):
- glog
Expand Down Expand Up @@ -1697,6 +1718,7 @@ DEPENDENCIES:
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
- "react-native-skia (from `../../../node_modules/@shopify/react-native-skia`)"
- "react-native-slider (from `../../../node_modules/@react-native-community/slider`)"
- react-native-wgpu (from `../../../node_modules/react-native-wgpu`)
- React-nativeconfig (from `../../../node_modules/react-native/ReactCommon`)
- React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`)
Expand Down Expand Up @@ -1811,6 +1833,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/@shopify/react-native-skia"
react-native-slider:
:path: "../../../node_modules/@react-native-community/slider"
react-native-wgpu:
:path: "../../../node_modules/react-native-wgpu"
React-nativeconfig:
:path: "../../../node_modules/react-native/ReactCommon"
React-NativeModulesApple:
Expand Down Expand Up @@ -1913,6 +1937,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: ab8f4a3d8180913bd78ae75dd599c94cce3d5e9a
react-native-skia: b1f33ae82bb728aee1d1602a6adfaee52af43f61
react-native-slider: 97ce0bd921f40de79cead9754546d5e4e7ba44f8
react-native-wgpu: fb9d60b0f4c63a03fb60bd986d758aeffca6ee13
React-nativeconfig: 57781b79e11d5af7573e6f77cbf1143b71802a6d
React-NativeModulesApple: 7ff2e2cfb2e5fa5bdedcecf28ce37e696c6ef1e1
React-perflogger: 8a360ccf603de6ddbe9ff8f54383146d26e6c936
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
6 changes: 6 additions & 0 deletions apps/paper/jestSetup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ jest.mock("expo-asset", () => ({
useAssets: () => [[], undefined],
}));

jest.mock("react-native-wgpu", () => {
return {
Canvas: jest.fn(),
};
});

jest.mock("react-native-reanimated", () => {
// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
Expand Down
1 change: 1 addition & 0 deletions apps/paper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"react-native-safe-area-context": "^4.10.9",
"react-native-screens": "^3.34.0",
"react-native-svg": "^15.6.0",
"react-native-wgpu": "^0.1.7",
"typescript": "^5.2.2"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions apps/paper/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
FrostedCard,
SpeedTest,
Video,
WebGPU,
} from "./Examples";
import { CI, Tests } from "./Tests";
import { HomeScreen } from "./Home";
Expand Down Expand Up @@ -108,6 +109,14 @@ const App = () => {
title: "🎨 Skia",
}}
/>
<Stack.Screen
name="WebGPU"
key="WebGPU"
component={WebGPU}
options={{
title: "🏔️ WebGPU",
}}
/>
<Stack.Screen
key="Tests"
name="Tests"
Expand Down
39 changes: 39 additions & 0 deletions apps/paper/src/Examples/WebGPU/WebGPU.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import { StyleSheet, View } from "react-native";
import { useFrameCallback } from "react-native-reanimated";
import { Canvas } from "react-native-wgpu";

import { useLoop } from "../../components/Animations";

import { drawBreatheDemo, useSkiaContext } from "./utils";

export function WebGPU() {
const { ref, context } = useSkiaContext();

const progress = useLoop({ duration: 3000 });

useFrameCallback(() => {
if (!context.value) {
return;
}

const ctx = context.value;
drawBreatheDemo(ctx, progress.value);
ctx.present();
});

return (
<View style={style.container}>
<Canvas ref={ref} style={style.webgpu} />
</View>
);
}

const style = StyleSheet.create({
container: {
flex: 1,
},
webgpu: {
flex: 1,
},
});
1 change: 1 addition & 0 deletions apps/paper/src/Examples/WebGPU/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./WebGPU";
76 changes: 76 additions & 0 deletions apps/paper/src/Examples/WebGPU/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { SkiaContext } from "@shopify/react-native-skia";
import {
BlendMode,
BlurStyle,
mix,
polar2Canvas,
Skia,
} from "@shopify/react-native-skia";
import { Dimensions, PixelRatio } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import { useCanvasEffect } from "react-native-wgpu";

export const useSkiaContext = () => {
const context = useSharedValue<SkiaContext | null>(null);
const ref = useCanvasEffect(() => {
const nativeSurface = ref.current!.getNativeSurface();
context.value = Skia.Context(
nativeSurface.surface,
nativeSurface.width * pd,
nativeSurface.height * pd
);
});
return {
context,
ref,
};
};

const pd = PixelRatio.get();
const { width, height } = Dimensions.get("window");
const center = { x: width / 2, y: height / 2 };
const R = width / 4;

const c1 = "#61bea2";
const c2 = "#529ca0";
const root = Skia.Paint();
root.setBlendMode(BlendMode.Screen);
root.setMaskFilter(Skia.MaskFilter.MakeBlur(BlurStyle.Solid, 10, true));
const p1 = root.copy();
p1.setColor(Skia.Color(c1));
const p2 = root.copy();
p2.setColor(Skia.Color(c2));

export const drawBreatheDemo = (ctx: SkiaContext, progress: number) => {
"worklet";
const surface = ctx.getSurface();
const canvas = surface.getCanvas();
canvas.clear(Skia.Color("rgb(36, 43, 56)"));
canvas.save();
canvas.scale(pd, pd);
canvas.rotate(progress * -180, center.x, center.y);
// const offscreen = Skia.Surface.MakeOffscreen(256, 256)!;
// const offscreenCanvas = offscreen.getCanvas();
// offscreenCanvas.clear(Skia.Color("green"));
// canvas.drawImage(offscreen.makeImageSnapshot(), 0, 0);
new Array(6).fill(0).map((_, index) => {
canvas.save();
const theta = (index * (2 * Math.PI)) / 6;
const { x, y } = polar2Canvas(
{ theta, radius: progress * R },
{ x: 0, y: 0 }
);
const scale = mix(progress, 0.3, 1);

canvas.translate(center.x, center.y);
canvas.translate(x, y);
canvas.scale(scale, scale);
canvas.translate(-center.x, -center.y);

const paint = index % 2 ? p1 : p2;
canvas.drawCircle(center.x, center.y, R, paint);
canvas.restore();
});

canvas.restore();
};
1 change: 1 addition & 0 deletions apps/paper/src/Examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export * from "./Stickers";
export * from "./FrostedCard";
export * from "./SpeedTest";
export * from "./Video";
export * from "./WebGPU";
1 change: 1 addition & 0 deletions apps/paper/src/Home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const HomeScreen = () => {
route="API"
testId="API"
/>
<HomeScreenButton title="🏔️ WebGPU" description="WebGPU" route="WebGPU" />
<HomeScreenButton
title="🎥 Reanimated"
description="Reanimated & Gesture Handler"
Expand Down
1 change: 1 addition & 0 deletions apps/paper/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type StackParamList = {
WebGPU: undefined;
Tests: {
title?: string;
path?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ class RNSkAndroidPlatformContext : public RNSkPlatformContext {
return SkiaOpenGLSurfaceFactory::makeOffscreenSurface(width, height);
}

std::shared_ptr<SkiaContext>
makeContextFromNativeSurface(void *surface, int width, int height) override {
return SkiaOpenGLSurfaceFactory::makeContext(
reinterpret_cast<ANativeWindow *>(surface), width, height);
}

sk_sp<SkImage> makeImageFromNativeBuffer(void *buffer) override {
return SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(buffer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,85 @@ sk_sp<SkSurface> WindowSurfaceHolder::getSurface() {
return _skSurface;
}

sk_sp<SkSurface> AndroidSkiaContext::getSurface() {
if (_skSurface == nullptr) {

// Setup OpenGL and Skia
if (!SkiaOpenGLHelper::createSkiaDirectContextIfNecessary(
&ThreadContextHolder::ThreadSkiaOpenGLContext)) {
RNSkLogger::logToConsole(
"Could not create Skia Surface from native window / surface. "
"Failed creating Skia Direct Context");
return nullptr;
}

// Now we can create a surface
_glSurface = SkiaOpenGLHelper::createWindowedSurface(_window);
if (_glSurface == EGL_NO_SURFACE) {
RNSkLogger::logToConsole(
"Could not create EGL Surface from native window / surface.");
return nullptr;
}

// Now make this one current
if (!SkiaOpenGLHelper::makeCurrent(
&ThreadContextHolder::ThreadSkiaOpenGLContext, _glSurface)) {
RNSkLogger::logToConsole(
"Could not create EGL Surface from native window / surface. Could "
"not set new surface as current surface.");
return nullptr;
}

// Set up parameters for the render target so that it
// matches the underlying OpenGL context.
GrGLFramebufferInfo fboInfo;

// We pass 0 as the framebuffer id, since the
// underlying Skia GrGlGpu will read this when wrapping the context in the
// render target and the GrGlGpu object.
fboInfo.fFBOID = 0;
fboInfo.fFormat = 0x8058; // GL_RGBA8

GLint stencil;
glGetIntegerv(GL_STENCIL_BITS, &stencil);

GLint samples;
glGetIntegerv(GL_SAMPLES, &samples);

auto colorType = kN32_SkColorType;

auto maxSamples =
ThreadContextHolder::ThreadSkiaOpenGLContext.directContext
->maxSurfaceSampleCountForColorType(colorType);

if (samples > maxSamples) {
samples = maxSamples;
}

auto renderTarget = GrBackendRenderTargets::MakeGL(_width, _height, samples,
stencil, fboInfo);

SkSurfaceProps props(0, kUnknown_SkPixelGeometry);

struct ReleaseContext {
EGLSurface glSurface;
};

auto releaseCtx = new ReleaseContext({_glSurface});

// Create surface object
_skSurface = SkSurfaces::WrapBackendRenderTarget(
ThreadContextHolder::ThreadSkiaOpenGLContext.directContext.get(),
renderTarget, kBottomLeft_GrSurfaceOrigin, colorType, nullptr, &props,
[](void *addr) {
auto releaseCtx = reinterpret_cast<ReleaseContext *>(addr);
SkiaOpenGLHelper::destroySurface(releaseCtx->glSurface);
delete releaseCtx;
},
reinterpret_cast<void *>(releaseCtx));
}

return _skSurface;
}

} // namespace RNSkia
Loading

0 comments on commit 970fe0d

Please sign in to comment.