From 22ed9cf3e0829a659b3cd0cb76b6774fa9f9af69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Sun, 20 Dec 2015 11:55:41 +0100 Subject: [PATCH] Bugfixes and Features of 2.17.x --- .eslintrc | 1 + Examples/AdvancedEffects/android/app/app.iml | 6 +- .../AdvancedEffects/android/app/build.gradle | 2 +- .../com/advancedeffects/MainActivity.java | 3 + Examples/AdvancedEffects/package.json | 2 +- Examples/AdvancedEffects/src/Banner.js | 2 +- Examples/AdvancedEffects/src/Slideshow.js | 4 +- Examples/AdvancedEffects/src/Vignette.js | 1 + Examples/Simple/android/app/app.iml | 6 +- Examples/Simple/android/app/build.gradle | 2 +- Examples/Simple/index.ios.js | 3 +- Examples/Simple/package.json | 2 +- Examples/Simple/src/OneFingerResponse.js | 79 ++++++---- Examples/Simple/src/index.js | 47 ++++-- Examples/Tests/android/app/app.iml | 6 +- Examples/Tests/android/app/build.gradle | 2 +- .../Tests/iOS/Tests.xcodeproj/project.pbxproj | 4 +- Examples/Tests/package.json | 2 +- Examples/android/RNGL.iml | 105 +++++++++++++ README.md | 36 ++++- android/RNGL.iml | 105 +++++++++++++ android/build.gradle | 2 +- .../com/projectseptember/RNGL/GLCanvas.java | 133 +++++++++++------ .../RNGL/GLCanvasManager.java | 62 +++++--- ios/GLCanvas.h | 9 +- ios/GLCanvas.m | 141 +++++++++--------- ios/GLCanvasManager.m | 13 +- ios/GLImage.m | 4 +- ios/GLImageData.h | 5 + ios/GLImageData.m | 63 +++++++- ios/GLTexture.h | 6 - ios/GLTexture.m | 102 +------------ ios/RNGL.xcodeproj/project.pbxproj | 6 +- ios/RNGLContext.m | 2 +- package.json | 5 +- src/GLCanvas.captureFrame.android.js | 15 ++ src/GLCanvas.captureFrame.ios.js | 13 ++ src/GLCanvas.js | 52 +++++-- src/Surface.js | 12 +- src/index.js | 7 + 40 files changed, 717 insertions(+), 355 deletions(-) create mode 100644 Examples/android/RNGL.iml create mode 100644 android/RNGL.iml create mode 100644 src/GLCanvas.captureFrame.android.js create mode 100644 src/GLCanvas.captureFrame.ios.js diff --git a/.eslintrc b/.eslintrc index 4a515a3..aa5082c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,5 @@ { + "parser": "babel-eslint", "globals": { "requestAnimationFrame": true }, diff --git a/Examples/AdvancedEffects/android/app/app.iml b/Examples/AdvancedEffects/android/app/app.iml index 7d9586a..2cd6263 100644 --- a/Examples/AdvancedEffects/android/app/app.iml +++ b/Examples/AdvancedEffects/android/app/app.iml @@ -78,7 +78,7 @@ - + @@ -97,15 +97,15 @@ - + + - diff --git a/Examples/AdvancedEffects/android/app/build.gradle b/Examples/AdvancedEffects/android/app/build.gradle index 30541b8..56c9895 100644 --- a/Examples/AdvancedEffects/android/app/build.gradle +++ b/Examples/AdvancedEffects/android/app/build.gradle @@ -74,7 +74,7 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.16.+" + compile "com.facebook.react:react-native:0.17.+" compile project(':RNGL') } diff --git a/Examples/AdvancedEffects/android/app/src/main/java/com/advancedeffects/MainActivity.java b/Examples/AdvancedEffects/android/app/src/main/java/com/advancedeffects/MainActivity.java index 733e287..6e9d0e3 100644 --- a/Examples/AdvancedEffects/android/app/src/main/java/com/advancedeffects/MainActivity.java +++ b/Examples/AdvancedEffects/android/app/src/main/java/com/advancedeffects/MainActivity.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; +import android.view.View; import com.facebook.react.LifecycleState; import com.facebook.react.ReactInstanceManager; @@ -34,6 +35,8 @@ protected void onCreate(Bundle savedInstanceState) { mReactRootView.startReactApplication(mReactInstanceManager, "AdvancedEffects", null); setContentView(mReactRootView); + + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN); } @Override diff --git a/Examples/AdvancedEffects/package.json b/Examples/AdvancedEffects/package.json index 70137d0..d3b5ae1 100644 --- a/Examples/AdvancedEffects/package.json +++ b/Examples/AdvancedEffects/package.json @@ -9,6 +9,6 @@ "gl-react": "^2.0.2", "gl-react-native": "file:../..", "glsl-transitions": "^2015.11.8", - "react-native": "^0.16.0" + "react-native": "^0.17.0" } } diff --git a/Examples/AdvancedEffects/src/Banner.js b/Examples/AdvancedEffects/src/Banner.js index e8bbe7f..c9f5285 100644 --- a/Examples/AdvancedEffects/src/Banner.js +++ b/Examples/AdvancedEffects/src/Banner.js @@ -27,7 +27,7 @@ void main( void ) { class Banner extends React.Component { render () { const { width, height, time } = this.props; - return + return console.log("Banner onLoad")}> ; } diff --git a/Examples/AdvancedEffects/src/Slideshow.js b/Examples/AdvancedEffects/src/Slideshow.js index 20ca69c..fc1f232 100644 --- a/Examples/AdvancedEffects/src/Slideshow.js +++ b/Examples/AdvancedEffects/src/Slideshow.js @@ -34,7 +34,9 @@ class Slideshow extends React.Component { const transitionUniforms = this._uniforms; return - + console.log("Slideshow onLoad")} + onProgress={e => console.log("Slideshow onProgress", e.nativeEvent)}> true} onMoveShouldSetResponder={() => true} + onLoad={() => console.log("Vignette onLoad")} onResponderMove={this.onResponderMove}> - + @@ -96,15 +96,15 @@ - + + - diff --git a/Examples/Simple/android/app/build.gradle b/Examples/Simple/android/app/build.gradle index 4bcd0ed..c24a17d 100644 --- a/Examples/Simple/android/app/build.gradle +++ b/Examples/Simple/android/app/build.gradle @@ -74,7 +74,7 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.16.+" + compile "com.facebook.react:react-native:0.17.+" compile project(":RNMaterialKit") compile project(":RNGL") diff --git a/Examples/Simple/index.ios.js b/Examples/Simple/index.ios.js index 18646c8..d29231f 100644 --- a/Examples/Simple/index.ios.js +++ b/Examples/Simple/index.ios.js @@ -1,3 +1,4 @@ -const { AppRegistry } = require("react-native"); +const { AppRegistry, StatusBarIOS } = require("react-native"); const Simple = require("./src"); +StatusBarIOS.setHidden(true); AppRegistry.registerComponent("Simple", () => Simple); diff --git a/Examples/Simple/package.json b/Examples/Simple/package.json index e42ff4e..36c439e 100644 --- a/Examples/Simple/package.json +++ b/Examples/Simple/package.json @@ -8,7 +8,7 @@ "dependencies": { "gl-react-native": "file:../..", "gl-react": "^2.0.2", - "react-native": "^0.16.0", + "react-native": "^0.17.0", "react-native-material-kit": "^0.2.2" } } diff --git a/Examples/Simple/src/OneFingerResponse.js b/Examples/Simple/src/OneFingerResponse.js index 0841cec..8cede14 100644 --- a/Examples/Simple/src/OneFingerResponse.js +++ b/Examples/Simple/src/OneFingerResponse.js @@ -2,6 +2,8 @@ const React = require("react-native"); const GL = require("gl-react"); const {Surface} = require("gl-react-native"); +const {PanResponder, UIManager} = React; + const shaders = GL.Shaders.create({ oneFingerResponse: { frag: ` @@ -26,44 +28,57 @@ class OneFingerResponse extends React.Component { super(props); this.state = { pressed: 0, - position: [ 0, 0 ] + position: [ 0, 0 ], + surfaceBound: [ 0, 0, 1, 1 ] // x, y, w, h }; - this.onTouchStart = this.onTouchStart.bind(this); - this.onTouchEnd = this.onTouchEnd.bind(this); - this.onTouchMove = this.onTouchMove.bind(this); - } - onTouchStart (evt) { - this.setState({ - pressed: 1 - }); - this.onTouchMove(evt); - } - onTouchMove (evt) { - const { width, height } = this.props; - const { locationX, locationY } = evt.nativeEvent; - this.setState({ - position: [ - Math.max(0, Math.min(locationX/width, 1)), - Math.max(0, Math.min(1-locationY/height, 1)) - ] - }); - } - onTouchEnd () { - this.setState({ - pressed: 0 + + this._panResponder = PanResponder.create({ + onStartShouldSetPanResponder: (evt, gestureState) => true, + onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + onMoveShouldSetPanResponder: (evt, gestureState) => true, + onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + + onPanResponderGrant: (evt, gestureState) => + UIManager.measure( + React.findNodeHandle(this.refs.surface), + (a, b, w, h, x, y) => + this.setState({ + pressed: 1, + surfaceBound: [x,y,w,h], + position: [ gestureState.x0, gestureState.y0 ] + })), + + onPanResponderMove: (evt, gestureState) => + this.setState({ + position: [ gestureState.x0 + gestureState.dx, gestureState.y0 + gestureState.dy ] + }), + + onPanResponderTerminationRequest: (evt, gestureState) => true, + + onPanResponderRelease: (evt, gestureState) => + this.setState({ + pressed: 0 + }), + + onPanResponderTerminate: (evt, gestureState) => + this.setState({ + pressed: 0 + }), + + onShouldBlockNativeResponder: (evt, gestureState) => true }); + } render () { const { width, height } = this.props; - const { pressed, position } = this.state; + const { pressed, position:[x,y], surfaceBound: [sx,sy,sw,sh] } = this.state; + const position = [ + (x - sx) / sw, + 1 - (y - sy) / sh + ]; return true} - onMoveShouldSetResponderCapture={() => true} - onResponderTerminationRequest={() => false} - onResponderGrant={this.onTouchStart} - onResponderMove={this.onTouchMove} - onResponderRelease={this.onTouchEnd} - onResponderTerminate={this.onTouchEnd} + ref="surface" + {...this._panResponder.panHandlers} width={width} height={height}> { + this.refs.helloGL.captureFrame().then(data64 => { this.setState({ captured: data64 }); }); } @@ -108,21 +118,22 @@ class Simple extends Component { return - Welcome to GL React Native! + gl-react-native > Simple this.setState({ current })} value={current}> - + - {captured && } + {captured && } + {captured && {captured.slice(0, 100)}} - + - - - + + + + + @@ -217,7 +231,7 @@ class Simple extends Component { width={256} height={160} factor={factor/2}> - + + Note: This is highly experimental and not yet performant enough. diff --git a/Examples/Tests/android/app/app.iml b/Examples/Tests/android/app/app.iml index 521052c..991e656 100644 --- a/Examples/Tests/android/app/app.iml +++ b/Examples/Tests/android/app/app.iml @@ -77,7 +77,7 @@ - + @@ -96,15 +96,15 @@ - + + - diff --git a/Examples/Tests/android/app/build.gradle b/Examples/Tests/android/app/build.gradle index 97b9dd8..bc93951 100644 --- a/Examples/Tests/android/app/build.gradle +++ b/Examples/Tests/android/app/build.gradle @@ -74,7 +74,7 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.16.+" + compile "com.facebook.react:react-native:0.17.+" compile project(":RNGL") } diff --git a/Examples/Tests/iOS/Tests.xcodeproj/project.pbxproj b/Examples/Tests/iOS/Tests.xcodeproj/project.pbxproj index 021ccbc..97e8a0c 100644 --- a/Examples/Tests/iOS/Tests.xcodeproj/project.pbxproj +++ b/Examples/Tests/iOS/Tests.xcodeproj/project.pbxproj @@ -21,7 +21,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 3461EB301C132AA90003E4A2 /* libRNGL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3461EB2F1C132A9F0003E4A2 /* libRNGL.a */; }; + 34562A0D1C26B83E0079DC6F /* libRNGL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3461EB2F1C132A9F0003E4A2 /* libRNGL.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -149,7 +149,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3461EB301C132AA90003E4A2 /* libRNGL.a in Frameworks */, + 34562A0D1C26B83E0079DC6F /* libRNGL.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, diff --git a/Examples/Tests/package.json b/Examples/Tests/package.json index da7ebde..5e12bf4 100644 --- a/Examples/Tests/package.json +++ b/Examples/Tests/package.json @@ -8,6 +8,6 @@ "dependencies": { "gl-react-native": "file:../..", "gl-react": "^2.0.2", - "react-native": "^0.16.0" + "react-native": "^0.17.0" } } diff --git a/Examples/android/RNGL.iml b/Examples/android/RNGL.iml new file mode 100644 index 0000000..e3cb714 --- /dev/null +++ b/Examples/android/RNGL.iml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 296076a..7bf3acc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ **[Gitbook documentation](http://projectseptemberinc.gitbooks.io/gl-react/content/) / [Github](https://github.com/ProjectSeptemberInc/gl-react-native/) / [gl-react](https://github.com/ProjectSeptemberInc/gl-react/)** / [#gl-react on reactiflux](https://discordapp.com/channels/102860784329052160/106102146109325312) -# icon gl-react-native ![](https://img.shields.io/badge/react--native-%3E=%200.16.0-05F561.svg) +# icon gl-react-native ![](https://img.shields.io/badge/react--native-%200.17.x-05F561.svg) OpenGL bindings for React Native to implement complex effects over images and components, in the descriptive VDOM paradigm. @@ -14,14 +14,38 @@ OpenGL bindings for React Native to implement complex effects over images and co ## Installation -a few steps are required to install `gl-react-native`: - -**Install the dependency to your React Native application:** - ``` npm i --save gl-react-native ``` -**Configure your React Native Application:** +### Configure your React Native Application + +**on iOS:** ![](https://github.com/ProjectSeptemberInc/gl-react-native/raw/master/docs/install-steps.png) + +**on Android:** + +1. `android/settings.gradle`:: Add the following snippet + ```gradle + include ':RNGL' + project(':RNGL').projectDir = file('../node_modules/gl-react-native/android') + ``` +1. `android/app/build.gradle`: Add in dependencies block. + ```gradle + compile project(':RNGL') + ``` +1. in your `MainActivity` (or equivalent): + ```java + import com.projectseptember.RNGL.RNGLPackage; + ... + + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + ... + .addPackage(new MainReactPackage()) + .addPackage(new RNGLPackage()) + ... + .build(); + + ``` diff --git a/android/RNGL.iml b/android/RNGL.iml new file mode 100644 index 0000000..0ee7994 --- /dev/null +++ b/android/RNGL.iml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 43f1d0d..9c3c76c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,5 +30,5 @@ repositories { } dependencies { - compile 'com.facebook.react:react-native:0.16.+' + compile 'com.facebook.react:react-native:0.17.+' } diff --git a/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java b/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java index a7e8d6c..d7a8919 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java @@ -6,7 +6,9 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.net.Uri; +import android.opengl.GLException; import android.opengl.GLSurfaceView; +import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; @@ -19,9 +21,12 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.PointerEvents; +import com.facebook.react.uimanager.ReactPointerEventsView; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.events.RCTEventEmitter; +import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -37,7 +42,8 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -public class GLCanvas extends GLSurfaceView implements GLSurfaceView.Renderer, Executor { +public class GLCanvas extends GLSurfaceView + implements GLSurfaceView.Renderer, Executor, ReactPointerEventsView { private ReactContext reactContext; private RNGLContext rnglContext; @@ -60,6 +66,7 @@ public class GLCanvas extends GLSurfaceView implements GLSurfaceView.Renderer, E private Map fbos; private ExecutorSupplier executorSupplier; private final Queue mRunOnDraw = new LinkedList<>(); + private boolean captureFrameRequested = false; public GLCanvas(ThemedReactContext context, ExecutorSupplier executorSupplier) { super(context); @@ -120,8 +127,6 @@ public void onDrawFrame(GL10 gl) { if (contentTextures.size() != this.nbContentTextures) resizeUniformContentTextures(nbContentTextures); - syncEventsThrough(); // FIXME: need to do this here? - if (!preloadingDone) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -144,6 +149,15 @@ public void run() { if (shouldRenderNow) { this.render(); deferredRendering = false; + if (captureFrameRequested) { + captureFrameRequested = false; + Bitmap capture = createSnapshot(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + capture.compress(Bitmap.CompressFormat.PNG, 100, baos); + String frame = "data:image/png;base64,"+ + Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT); + dispatchOnCaptureFrame(frame); + } } } @@ -174,36 +188,6 @@ public void setAutoRedraw(boolean autoRedraw) { this.setRenderMode(autoRedraw ? GLSurfaceView.RENDERMODE_CONTINUOUSLY : GLSurfaceView.RENDERMODE_WHEN_DIRTY); } - public void setEventsThrough(boolean eventsThrough) { - syncEventsThrough(); - } - - public void setVisibleContent(boolean visibleContent) { - syncEventsThrough(); - } - - public void setCaptureNextFrameId(int captureNextFrameId) { - // FIXME move away from this pattern. just use a method, same to ObjC impl - this.requestRender(); - } - - private boolean ensureCompiledShader (List data) { - for (GLData d: data) { - if (!ensureCompiledShader(d)) { - return false; - } - } - return true; - } - - private boolean ensureCompiledShader (GLData data) { - GLShader shader = getShader(data.shader); - return shader != null && - shader.ensureCompile() && - ensureCompiledShader(data.children) && - ensureCompiledShader(data.contextChildren); - } - public void setData (GLData data) { this.data = data; if (preloadingDone) syncContentBitmaps(); @@ -247,9 +231,7 @@ private void runAll(Queue queue) { public void requestSyncData () { execute(new Runnable() { public void run() { - if (ensureCompiledShader(data)) - syncData(); - else + if (!syncData()) requestSyncData(); } }); @@ -325,7 +307,6 @@ public void resizeUniformContentTextures (int n) { private int countPreloaded () { int nb = 0; for (Uri toload: imagesToPreload) { - Log.i("GLCanvas", "toload: "+toload.getPath()+" = "+preloaded.contains(toload)); if (preloaded.contains(toload)) { nb++; } @@ -383,6 +364,7 @@ private GLRenderData recSyncData (GLData data, HashMap images) { Map prevImages = this.images; GLShader shader = getShader(data.shader); + if (shader == null || !shader.ensureCompile()) return null; Map uniformsInteger = new HashMap<>(); Map uniformsFloat = new HashMap<>(); Map uniformsIntBuffer = new HashMap<>(); @@ -392,11 +374,15 @@ private GLRenderData recSyncData (GLData data, HashMap images) { List children = new ArrayList<>(); for (GLData child: data.contextChildren) { - contextChildren.add(recSyncData(child, images)); + GLRenderData node = recSyncData(child, images); + if (node == null) return null; + contextChildren.add(node); } for (GLData child: data.children) { - children.add(recSyncData(child, images)); + GLRenderData node = recSyncData(child, images); + if (node == null) return null; + children.add(node); } Map uniformTypes = shader.getUniformTypes(); @@ -595,11 +581,14 @@ private int arraySizeForType(int type) { } } - private void syncData () { - if (data == null) return; + private boolean syncData () { + if (data == null) return true; HashMap images = new HashMap<>(); - renderData = recSyncData(data, images); + GLRenderData node = recSyncData(data, images); + if (node == null) return false; + renderData = node; this.images = images; + return true; } private void recRender (GLRenderData renderData) { @@ -665,12 +654,16 @@ private void render () { glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); } - private void syncEventsThrough () { - // TODO: figure out how to do this... - // For some reason, the click through is half working + private void dispatchOnCaptureFrame (String frame) { + WritableMap event = Arguments.createMap(); + event.putString("frame", frame); + ReactContext reactContext = (ReactContext)getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + getId(), + "captureFrame", + event); } - private void dispatchOnProgress (double progress, int loaded, int total) { WritableMap event = Arguments.createMap(); event.putDouble("progress", progress); @@ -691,4 +684,52 @@ private void dispatchOnLoad () { "load", event); } + + public void requestCaptureFrame() { + captureFrameRequested = true; + this.requestRender(); + } + + private Bitmap createSnapshot () { + return createSnapshot(0, 0, getWidth(), getHeight()); + } + + private Bitmap createSnapshot (int x, int y, int w, int h) { + int bitmapBuffer[] = new int[w * h]; + int bitmapSource[] = new int[w * h]; + IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer); + intBuffer.position(0); + + try { + glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, intBuffer); + int offset1, offset2; + for (int i = 0; i < h; i++) { + offset1 = i * w; + offset2 = (h - i - 1) * w; + for (int j = 0; j < w; j++) { + int texturePixel = bitmapBuffer[offset1 + j]; + int blue = (texturePixel >> 16) & 0xff; + int red = (texturePixel << 16) & 0x00ff0000; + int pixel = (texturePixel & 0xff00ff00) | red | blue; + bitmapSource[offset2 + j] = pixel; + } + } + } catch (GLException e) { + return null; + } + + return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888); + } + + private PointerEvents mPointerEvents = PointerEvents.AUTO; + + @Override + public PointerEvents getPointerEvents() { + return mPointerEvents; + } + + void setPointerEvents(PointerEvents pointerEvents) { + mPointerEvents = pointerEvents; + } + } diff --git a/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java b/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java index 560d161..36fc95f 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java @@ -6,13 +6,16 @@ import com.facebook.imagepipeline.core.ExecutorSupplier; import com.facebook.imagepipeline.memory.PoolConfig; import com.facebook.imagepipeline.memory.PoolFactory; +import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ReactProp; +import java.util.Locale; import java.util.Map; @@ -20,12 +23,15 @@ public class GLCanvasManager extends SimpleViewManager { public static final String REACT_CLASS = "GLCanvas"; + public static final int COMMAND_CAPTURE_FRAME = 1; + private ExecutorSupplier executorSupplier; @ReactProp(name="nbContentTextures") public void setNbContentTextures (GLCanvas view, int nbContentTextures) { view.setNbContentTextures(nbContentTextures); } + @ReactProp(name="renderId") public void setRenderId (GLCanvas view, int renderId) { view.setRenderId(renderId); @@ -36,27 +42,20 @@ public void setOpaque (GLCanvas view, boolean opaque) { view.setOpaque(opaque); } - @ReactProp(name="autoRedraw") + @ReactProp(name = "autoRedraw") public void setAutoRedraw (GLCanvas view, boolean autoRedraw) { view.setAutoRedraw(autoRedraw); } - @ReactProp(name="eventsThrough") - public void setEventsThrough (GLCanvas view, boolean eventsThrough) { - view.setEventsThrough(eventsThrough); - } - - @ReactProp(name="visibleContent") - public void setVisibleContent (GLCanvas view, boolean visibleContent) { - view.setVisibleContent(visibleContent); - } - - @ReactProp(name="captureNextFrameId") - public void setCaptureNextFrameId (GLCanvas view, int captureNextFrameId) { - view.setCaptureNextFrameId(captureNextFrameId); + @ReactProp(name = "pointerEvents") + public void setPointerEvents(GLCanvas view, @Nullable String pointerEventsStr) { + if (pointerEventsStr != null) { + PointerEvents pointerEvents = PointerEvents.valueOf(pointerEventsStr.toUpperCase(Locale.US).replace("-", "_")); + view.setPointerEvents(pointerEvents); + } } - @ReactProp(name="data") + @ReactProp(name = "data") public void setData (GLCanvas view, @Nullable ReadableMap data) { view.setData(data == null ? null : GLData.fromMap(data)); } @@ -81,13 +80,42 @@ public GLCanvas createViewInstance (ThemedReactContext context) { return new GLCanvas(context, executorSupplier); } + @Override + public void receiveCommand( + GLCanvas canvas, + int commandType, + @Nullable ReadableArray args) { + Assertions.assertNotNull(canvas); + Assertions.assertNotNull(args); + switch (commandType) { + case COMMAND_CAPTURE_FRAME: { + canvas.requestCaptureFrame(); + return; + } + default: + throw new IllegalArgumentException(String.format( + "Unsupported command %d received by %s.", + commandType, + getClass().getSimpleName())); + } + } + @Override public @Nullable Map getExportedCustomDirectEventTypeConstants() { return MapBuilder.of( + "captureFrame", + MapBuilder.of("registrationName", "onGLCaptureFrame"), "load", - MapBuilder.of("registrationName", "onLoad"), + MapBuilder.of("registrationName", "onGLLoad"), "progress", - MapBuilder.of("registrationName", "onProgress") + MapBuilder.of("registrationName", "onGLProgress") ); } + + @Override + public Map getCommandsMap() { + return MapBuilder.of( + "captureFrame", + COMMAND_CAPTURE_FRAME); + } } diff --git a/ios/GLCanvas.h b/ios/GLCanvas.h index 3ddd672..4f04cde 100644 --- a/ios/GLCanvas.h +++ b/ios/GLCanvas.h @@ -1,5 +1,6 @@ #import #import "GLData.h" +#import "RCTComponent.h" @interface GLCanvas: GLKView @@ -11,12 +12,12 @@ @property (nonatomic) NSNumber *nbContentTextures; @property (nonatomic) NSNumber *renderId; @property (nonatomic) NSArray *imagesToPreload; -@property (nonatomic, assign) BOOL onProgress; -@property (nonatomic, assign) BOOL onLoad; -@property (nonatomic, assign) BOOL onChange; +@property (nonatomic, copy) RCTBubblingEventBlock onGLProgress; +@property (nonatomic, copy) RCTBubblingEventBlock onGLLoad; +@property (nonatomic, copy) RCTBubblingEventBlock onGLCaptureFrame; - (instancetype)initWithBridge:(RCTBridge *)bridge; -- (void) capture:(RCTResponseSenderBlock)callback; +- (void) requestCaptureFrame; @end diff --git a/ios/GLCanvas.m b/ios/GLCanvas.m index ce52c30..3260fa8 100644 --- a/ios/GLCanvas.m +++ b/ios/GLCanvas.m @@ -12,7 +12,6 @@ #import "GLRenderData.h" #import "UIView+React.h" - NSString* srcResource (id res) { NSString *src; @@ -33,9 +32,10 @@ @implementation GLCanvas RCTBridge *_bridge; GLRenderData *_renderData; - - NSMutableArray *_captureListeners; + BOOL _captureFrameRequested; + + NSArray *_contentData; NSArray *_contentTextures; NSDictionary *_images; // This caches the currently used images (imageSrc -> GLReactImage) @@ -49,8 +49,8 @@ @implementation GLCanvas BOOL _preloadingDone; NSTimer *animationTimer; - - BOOL _needSync; + + BOOL _needSync; } - (instancetype)initWithBridge:(RCTBridge *)bridge @@ -59,7 +59,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge _bridge = bridge; _images = @{}; _preloaded = [[NSMutableArray alloc] init]; - _captureListeners = [[NSMutableArray alloc] init]; + _captureFrameRequested = false; _preloadingDone = false; self.context = [bridge.rnglContext getContext]; self.contentScaleFactor = RCTScreenScale(); @@ -71,9 +71,9 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge //// Props Setters -- (void) capture:(RCTResponseSenderBlock)callback +- (void) requestCaptureFrame { - [_captureListeners addObject:callback]; + _captureFrameRequested = true; [self setNeedsDisplay]; } @@ -121,16 +121,12 @@ - (void)setAutoRedraw:(BOOL)autoRedraw } } -- (void)setEventsThrough:(BOOL)eventsThrough +- (void)setPointerEvents:(RCTPointerEvents)pointerEvents { - _eventsThrough = eventsThrough; - [self syncEventsThrough]; -} - --(void)setVisibleContent:(BOOL)visibleContent -{ - _visibleContent = visibleContent; - [self syncEventsThrough]; + self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone); + if (pointerEvents == RCTPointerEventsBoxNone) { + self.accessibilityViewIsModal = NO; + } } - (void)setData:(GLData *)data @@ -141,22 +137,15 @@ - (void)setData:(GLData *)data - (void)setNbContentTextures:(NSNumber *)nbContentTextures { - [self resizeUniformContentTextures:[nbContentTextures intValue]]; _nbContentTextures = nbContentTextures; } //// Sync methods (called from props setters) -- (void) syncEventsThrough -{ - self.userInteractionEnabled = !(_eventsThrough); - self.superview.userInteractionEnabled = !(_eventsThrough && !_visibleContent); -} - - (void)requestSyncData { - _needSync = true; - [self setNeedsDisplay]; + _needSync = true; + [self setNeedsDisplay]; } - (void)syncData @@ -176,15 +165,20 @@ - (void)syncData NSMutableArray *contextChildren = [[NSMutableArray alloc] init]; for (GLData *child in data.contextChildren) { - [contextChildren addObject:weak_traverseTree(child)]; + GLRenderData *node = weak_traverseTree(child); + if (node == nil) return nil; + [contextChildren addObject:node]; } NSMutableArray *children = [[NSMutableArray alloc] init]; for (GLData *child in data.children) { - [children addObject:weak_traverseTree(child)]; + GLRenderData *node = weak_traverseTree(child); + if (node == nil) return nil; + [children addObject:node]; } GLShader *shader = [_bridge.rnglContext getShader:data.shader]; + if (shader == nil) return nil; NSDictionary *uniformTypes = [shader uniformTypes]; NSMutableDictionary *uniforms = [[NSMutableDictionary alloc] init]; @@ -194,12 +188,12 @@ - (void)syncData id value = [data.uniforms objectForKey:uniformName]; GLenum type = [uniformTypes[uniformName] intValue]; - + if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE) { uniforms[uniformName] = [NSNumber numberWithInt:units++]; if ([value isEqual:[NSNull null]]) { GLTexture *emptyTexture = [[GLTexture alloc] init]; - [emptyTexture setPixelsEmpty]; + [emptyTexture setPixels:nil]; textures[uniformName] = emptyTexture; } else { @@ -271,25 +265,40 @@ - (void)syncData withChildren:children]; }; - _renderData = traverseTree(_data); - _images = images; + GLRenderData *res = traverseTree(_data); + if (res != nil) { + _renderData = traverseTree(_data); + _images = images; + } } } -- (void)syncContentTextures +- (void)syncContentData { - int i = 0; - for (GLTexture *texture in _contentTextures) { - UIView* view = self.superview.subviews[i]; // We take siblings by index (closely related to the JS code) + NSMutableArray *contentData = [[NSMutableArray alloc] init]; + int nb = [_nbContentTextures intValue]; + for (int i = 0; i < nb; i++) { + UIView *view = self.superview.subviews[i]; // We take siblings by index (closely related to the JS code) + GLImageData *imgData = nil; if (view) { - if ([view.subviews count] == 1) - [texture setPixelsWithView:view.subviews[0]]; - else - [texture setPixelsWithView:view]; + UIView *v = [view.subviews count] == 1 ? + view.subviews[0] : + view; + imgData = [GLImageData genPixelsWithView:v]; } else { - [texture setPixelsEmpty]; + imgData = nil; } - i ++; + contentData[i] = imgData; + } + _contentData = contentData; +} + + +- (void)syncContentTextures +{ + unsigned long max = MIN([_contentData count], [_contentTextures count]); + for (int i=0; i 0) { - NSArray *listeners = _captureListeners; - _captureListeners = [[NSMutableArray alloc] init]; - + if (_captureFrameRequested) { + _captureFrameRequested = false; dispatch_async(dispatch_get_main_queue(), ^{ // snapshot not allowed in render tick. defer it. if (!weakSelf) return; UIImage *frameImage = [weakSelf snapshot]; NSData *frameData = UIImagePNGRepresentation(frameImage); NSString *frame = [NSString stringWithFormat:@"data:image/png;base64,%@", - [frameData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]]; - for (int i = 0; i < nbCaptureListeners; i++) { - RCTResponseSenderBlock listener = listeners[i]; - listener(@[[NSNull null], frame]); - } + [frameData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]]; + if (weakSelf.onGLCaptureFrame) weakSelf.onGLCaptureFrame(@{ @"frame": frame }); }); } } @@ -437,7 +441,6 @@ - (int)countPreloaded - (void)resizeUniformContentTextures:(int)n { - [EAGLContext setCurrentContext:self.context]; int length = (int) [_contentTextures count]; if (length == n) return; if (n < length) { @@ -454,25 +457,17 @@ - (void)resizeUniformContentTextures:(int)n - (void)dispatchOnLoad { - [_bridge.eventDispatcher sendInputEventWithName:@"load" body:@{ @"target": self.reactTag }]; + if (self.onGLLoad) self.onGLLoad(@{}); } - (void)dispatchOnProgress: (double)progress withLoaded:(int)loaded withTotal:(int)total { - NSDictionary *event = + if (self.onGLProgress) self.onGLProgress( @{ - @"target": self.reactTag, - @"progress": @(progress), - @"loaded": @(loaded), - @"total": @(total) }; - [_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event]; -} - -- (void)dispatchOnCapture: (NSString *)frame withId:(int)id -{ - NSDictionary *event = @{ @"target": self.reactTag, @"frame": frame, @"id":@(id) }; - // FIXME: using onChange is a hack before we use the new system to directly call callbacks. we will replace with: self.onCaptureNextFrame(...) - [_bridge.eventDispatcher sendInputEventWithName:@"change" body:event]; + @"progress": @(RCTZeroIfNaN(progress)), + @"loaded": @(RCTZeroIfNaN(loaded)), + @"total": @(RCTZeroIfNaN(total)) + }); } @end diff --git a/ios/GLCanvasManager.m b/ios/GLCanvasManager.m index ffa8abb..48c0212 100644 --- a/ios/GLCanvasManager.m +++ b/ios/GLCanvasManager.m @@ -20,26 +20,23 @@ - (instancetype)init RCT_EXPORT_VIEW_PROPERTY(nbContentTextures, NSNumber); RCT_EXPORT_VIEW_PROPERTY(opaque, BOOL); RCT_EXPORT_VIEW_PROPERTY(autoRedraw, BOOL); -RCT_EXPORT_VIEW_PROPERTY(eventsThrough, BOOL); -RCT_EXPORT_VIEW_PROPERTY(visibleContent, BOOL); RCT_EXPORT_VIEW_PROPERTY(data, GLData); RCT_EXPORT_VIEW_PROPERTY(renderId, NSNumber); RCT_EXPORT_VIEW_PROPERTY(imagesToPreload, NSArray); -RCT_EXPORT_VIEW_PROPERTY(onLoad, BOOL); -RCT_EXPORT_VIEW_PROPERTY(onProgress, BOOL); -RCT_EXPORT_VIEW_PROPERTY(onChange, BOOL); +RCT_EXPORT_VIEW_PROPERTY(onGLLoad, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onGLProgress, RCTBubblingEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onGLCaptureFrame, RCTBubblingEventBlock); -RCT_EXPORT_METHOD(capture: (nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback) +RCT_EXPORT_METHOD(capture: (nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { UIView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[GLCanvas class]]) { RCTLog(@"expecting UIView, got: %@", view); - callback(@[@"view is not a GLCanvas"]); } else { GLCanvas *glCanvas = (GLCanvas *)view; - [glCanvas capture:callback]; + [glCanvas requestCaptureFrame]; } }]; } diff --git a/ios/GLImage.m b/ios/GLImage.m index 9e47648..d2715bc 100644 --- a/ios/GLImage.m +++ b/ios/GLImage.m @@ -50,12 +50,12 @@ - (GLTexture *) getTexture { if (_image) { if (!_data) { - _data = genPixelsWithImage(_image); + _data = [GLImageData genPixelsWithImage:_image]; } [_texture setPixels:_data]; } else { - [_texture setPixelsEmpty]; + [_texture setPixels:nil]; } return _texture; } diff --git a/ios/GLImageData.h b/ios/GLImageData.h index 18d0f6f..c2010f3 100644 --- a/ios/GLImageData.h +++ b/ios/GLImageData.h @@ -6,6 +6,11 @@ @property (nonatomic) int width; @property (nonatomic) int height; ++ (GLImageData*) empty; ++ (GLImageData*) genPixelsWithImage: (UIImage *)image; ++ (GLImageData*) genPixelsWithView: (UIView *)view; + - (instancetype)initWithData: (GLubyte *)data withWidth:(int)width withHeight:(int)height; @end + diff --git a/ios/GLImageData.m b/ios/GLImageData.m index 1d5d150..5ef0c81 100644 --- a/ios/GLImageData.m +++ b/ios/GLImageData.m @@ -1,7 +1,7 @@ #import "GLImageData.h" - -// TODO: rename to GLImageData +#import "RCTUtils.h" +#import "RCTLog.h" // This structure aims to be used in an immutable way @implementation GLImageData @@ -11,6 +11,65 @@ @implementation GLImageData int _height; } +GLImageData *EMPTY_PIXELS; + ++ (GLImageData *)empty +{ + if (!EMPTY_PIXELS) { + int width = 2, height = 2; + GLubyte* data = (GLubyte *) malloc(width*height*4*sizeof(GLubyte)); + for (int i = 0; i < width * height * 4; i+=4) { + data[i] = data[i+1] = data[i+2] = 0; + data[i+3] = 0; + } + EMPTY_PIXELS = [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; + } + return EMPTY_PIXELS; +} + ++ (GLImageData *)genPixelsWithImage: (UIImage *)image +{ + int width = image.size.width; + int height = image.size.height; + if (width == 0 || height == 0) { + RCTLogError(@"The image must be loaded in setPixelsWithImage call"); + return nil; + } + GLubyte* data = malloc(width * height * 4); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef ctx = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (ctx == NULL) { + RCTLogError(@"unable to create the bitmap context"); + CGColorSpaceRelease(colorSpace); + free(data); + return nil; + } + CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height); + CGContextConcatCTM(ctx, flipVertical); + + CGRect rect = CGRectMake(0.0, 0.0, width, height); + CGContextClearRect(ctx, rect); + CGContextDrawImage(ctx, rect, image.CGImage); + CGColorSpaceRelease(colorSpace); + CGContextRelease(ctx); + return [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; +} + ++ (GLImageData *)genPixelsWithView: (UIView *)view +{ + float width = RCTScreenScale() * view.bounds.size.width; + float height = RCTScreenScale() * view.bounds.size.height; + GLubyte *data = (GLubyte *)malloc(4 * width * height); + CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef ctx = CGBitmapContextCreate(data, width, height, 8, 4 * width, colourSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + CGColorSpaceRelease(colourSpace); + CGContextClearRect(ctx, CGRectMake(0.0, 0.0, width, height)); + CGContextScaleCTM(ctx, RCTScreenScale(), RCTScreenScale()); + [view.layer renderInContext:ctx]; + CGContextRelease(ctx); + return [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; +} + - (instancetype)initWithData: (GLubyte *)data withWidth:(int)width withHeight:(int)height { self = [super init]; diff --git a/ios/GLTexture.h b/ios/GLTexture.h index 7837b19..e47019d 100644 --- a/ios/GLTexture.h +++ b/ios/GLTexture.h @@ -2,8 +2,6 @@ #import "RCTBridge.h" #import "GLImageData.h" -GLImageData* genPixelsWithImage (UIImage *image); - @interface GLTexture: NSObject @property EAGLContext *context; @@ -16,9 +14,5 @@ GLImageData* genPixelsWithImage (UIImage *image); - (void)setShapeWithWidth:(float)width withHeight:(float)height; - (void)setPixels: (GLImageData *)data; -- (void)setPixelsEmpty; -- (void)setPixelsRandom: (int)width withHeight:(int)height; -- (void)setPixelsWithImage: (UIImage *)image; -- (void)setPixelsWithView: (UIView *)view; @end diff --git a/ios/GLTexture.m b/ios/GLTexture.m index 8393520..865e452 100644 --- a/ios/GLTexture.m +++ b/ios/GLTexture.m @@ -2,84 +2,14 @@ #import "RCTLog.h" #import "RCTUtils.h" -GLImageData* genPixelsEmpty (int width, int height) -{ - GLubyte* data = (GLubyte *) malloc(width*height*4*sizeof(GLubyte)); - for (int i = 0; i < width * height * 4; i+=4) { - data[i] = data[i+1] = data[i+2] = 0; - data[i+3] = 0; - } - return [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; -} - -GLImageData* genPixelsRandom (int width, int height) -{ - GLubyte* data = (GLubyte *) malloc(width*height*4*sizeof(GLubyte)); - for (int i = 0; i < width * height * 4; i+=4) { - data[i] = rand() % 255; - data[i+1] = rand() % 255; - data[i+2] = rand() % 255; - data[i+3] = 255; - } - return [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; -} - -GLImageData* genPixelsWithImage (UIImage *image) -{ - int width = image.size.width; - int height = image.size.height; - if (width == 0 || height == 0) { - RCTLogError(@"The image must be loaded in setPixelsWithImage call"); - return nil; - } - GLubyte* data = malloc(width * height * 4); - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef ctx = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - if (ctx == NULL) { - RCTLogError(@"unable to create the bitmap context"); - CGColorSpaceRelease(colorSpace); - free(data); - return nil; - } - CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height); - CGContextConcatCTM(ctx, flipVertical); - - CGRect rect = CGRectMake(0.0, 0.0, width, height); - CGContextClearRect(ctx, rect); - CGContextDrawImage(ctx, rect, image.CGImage); - CGColorSpaceRelease(colorSpace); - CGContextRelease(ctx); - return [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; -} - -GLImageData* genPixelsWithView (UIView *view) -{ - float width = RCTScreenScale() * view.bounds.size.width; - float height = RCTScreenScale() * view.bounds.size.height; - GLubyte *data = (GLubyte *)malloc(4 * width * height); - CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef ctx = CGBitmapContextCreate(data, width, height, 8, 4 * width, colourSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - CGColorSpaceRelease(colourSpace); - CGContextClearRect(ctx, CGRectMake(0.0, 0.0, width, height)); - CGContextScaleCTM(ctx, RCTScreenScale(), RCTScreenScale()); - [view.layer renderInContext:ctx]; - CGContextRelease(ctx); - return [[GLImageData alloc] initWithData:data withWidth:width withHeight:height]; -} - @implementation GLTexture { GLuint _handle; // The identifier of the gl texture GLImageData* dataCurrentlyUploaded; // The last set data (cache) } -GLImageData *EMPTY_PIXELS; - - (instancetype)init { - if (!EMPTY_PIXELS) { - EMPTY_PIXELS = genPixelsEmpty(2, 2); - } self = [super init]; if (self) { [self makeTexture]; @@ -123,36 +53,12 @@ - (void)setShapeWithWidth:(float)width withHeight:(float)height - (void)setPixels: (GLImageData *)data { - if (data != dataCurrentlyUploaded) { - dataCurrentlyUploaded = data; + GLImageData *d = data==nil ? [GLImageData empty] : data; + if (d != dataCurrentlyUploaded) { + dataCurrentlyUploaded = d; [self bind]; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, data.width, data.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, d.width, d.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, d.data); } } -- (void)setPixelsEmpty -{ - [self setPixels:EMPTY_PIXELS]; -} - -- (void)setPixelsRandom: (int)width withHeight:(int)height // for testing -{ - GLImageData* data = genPixelsRandom(width, height); - [self setPixels:data]; -} - -- (void)setPixelsWithImage: (UIImage *)image -{ - GLImageData *data = genPixelsWithImage(image); - if (!data) return; - [self setPixels:data]; -} - -- (void)setPixelsWithView: (UIView *)view -{ - GLImageData *data = genPixelsWithView(view); - [self setPixels:data]; -} - - @end diff --git a/ios/RNGL.xcodeproj/project.pbxproj b/ios/RNGL.xcodeproj/project.pbxproj index 4c89e07..e056536 100644 --- a/ios/RNGL.xcodeproj/project.pbxproj +++ b/ios/RNGL.xcodeproj/project.pbxproj @@ -44,17 +44,17 @@ 346089C21BEFD0A500C90DB5 /* GLImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLImage.h; sourceTree = ""; }; 346089C31BEFD0A500C90DB5 /* GLImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLImage.m; sourceTree = ""; }; 346089C41BEFD0A500C90DB5 /* GLImageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLImageData.h; sourceTree = ""; }; - 346089C51BEFD0A500C90DB5 /* GLImageData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLImageData.m; sourceTree = ""; }; + 346089C51BEFD0A500C90DB5 /* GLImageData.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = GLImageData.m; sourceTree = ""; tabWidth = 2; }; 346089C61BEFD0A500C90DB5 /* GLRenderData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLRenderData.h; sourceTree = ""; }; 346089C71BEFD0A500C90DB5 /* GLRenderData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLRenderData.m; sourceTree = ""; }; 346089C81BEFD0A500C90DB5 /* GLShader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLShader.h; sourceTree = ""; }; 346089C91BEFD0A500C90DB5 /* GLShader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLShader.m; sourceTree = ""; }; 346089CA1BEFD0A500C90DB5 /* GLTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLTexture.h; sourceTree = ""; }; - 346089CB1BEFD0A500C90DB5 /* GLTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLTexture.m; sourceTree = ""; }; + 346089CB1BEFD0A500C90DB5 /* GLTexture.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = GLTexture.m; sourceTree = ""; tabWidth = 2; }; 346089CC1BEFD0A500C90DB5 /* RCTConvert+GLData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+GLData.h"; sourceTree = ""; }; 346089CD1BEFD0A500C90DB5 /* RCTConvert+GLData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+GLData.m"; sourceTree = ""; }; 346089CE1BEFD0A500C90DB5 /* RNGLContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGLContext.h; sourceTree = ""; }; - 346089CF1BEFD0A500C90DB5 /* RNGLContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGLContext.m; sourceTree = ""; }; + 346089CF1BEFD0A500C90DB5 /* RNGLContext.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RNGLContext.m; sourceTree = ""; tabWidth = 2; }; 4107012F1ACB723B00C6AA39 /* libRNGL.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNGL.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ diff --git a/ios/RNGLContext.m b/ios/RNGLContext.m index 340124b..689bb50 100644 --- a/ios/RNGLContext.m +++ b/ios/RNGLContext.m @@ -64,7 +64,7 @@ @implementation RCTBridge (RNGLContext) - (RNGLContext *)rnglContext { - return self.modules[RCTBridgeModuleNameForClass([RNGLContext class])]; + return [self moduleForClass:[RNGLContext class]]; } @end \ No newline at end of file diff --git a/package.json b/package.json index bb927c8..8615e98 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "author": "Project September ", "license": "MIT", "peerDependencies": { - "react-native": ">= 0.16.0", - "gl-react": ">= 2.0.2" + "react-native": ">= 0.17.0 <0.18.0", + "gl-react": ">= 2.0.3 <2.1.0" }, "dependencies": { "invariant": "2.2.0" }, "devDependencies": { + "babel-eslint": "^4.1.6", "eslint": "^1.9.0", "eslint-plugin-react": "^3.8.0" } diff --git a/src/GLCanvas.captureFrame.android.js b/src/GLCanvas.captureFrame.android.js new file mode 100644 index 0000000..ceb938b --- /dev/null +++ b/src/GLCanvas.captureFrame.android.js @@ -0,0 +1,15 @@ +const invariant = require("invariant"); +const React = require("react-native"); +const { + NativeModules: { UIManager } +} = React; +const {GLCanvas} = UIManager; +invariant(GLCanvas, +`gl-react-native: the native module is not available. +Make sure you have properly configured it. +See README install instructions. + +React.NativeModules.UIManager.GLCanvas is %s`, GLCanvas); +const {Commands} = GLCanvas; + +module.exports = handle => UIManager.dispatchViewManagerCommand(handle, Commands.captureFrame, []); diff --git a/src/GLCanvas.captureFrame.ios.js b/src/GLCanvas.captureFrame.ios.js new file mode 100644 index 0000000..24a716e --- /dev/null +++ b/src/GLCanvas.captureFrame.ios.js @@ -0,0 +1,13 @@ +const invariant = require("invariant"); +const React = require("react-native"); +const { + NativeModules: { GLCanvasManager } +} = React; +invariant(GLCanvasManager, +`gl-react-native: the native module is not available. +Make sure you have properly configured it. +See README install instructions. + +React.NativeModules.GLCanvasManager is %s`, GLCanvasManager); + +module.exports = handle => GLCanvasManager.capture(handle); diff --git a/src/GLCanvas.js b/src/GLCanvas.js index 6b5e347..79d8dc7 100644 --- a/src/GLCanvas.js +++ b/src/GLCanvas.js @@ -1,30 +1,54 @@ const React = require("react-native"); - const { Component, - requireNativeComponent, - NativeModules: { GLCanvasManager } + requireNativeComponent } = React; -const GLCanvasNative = requireNativeComponent("GLCanvas", GLCanvas); +const captureFrame = require("./GLCanvas.captureFrame"); -class GLCanvas extends Component { - constructor (props) { - super(props); +const GLCanvasNative = requireNativeComponent("GLCanvas", GLCanvas, { + nativeOnly: { + onGLChange: true, + onGLProgress: true, + onGLCaptureFrame: true } +}); + +function defer() { + const deferred = {}; + const promise = new Promise(function(resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + deferred.promise = promise; + return deferred; +} + +class GLCanvas extends Component { captureFrame (cb) { - GLCanvasManager.capture( - React.findNodeHandle(this.refs.native), - (error, frame) => { - if (error) console.error(error); // eslint-disable-line no-console - else cb(frame); - }); + const promise = ( + this._pendingCaptureFrame || // use pending capture OR create a new captureFrame pending + (captureFrame(React.findNodeHandle(this.refs.native)), this._pendingCaptureFrame = defer()) + ).promise; + if (typeof cb === "function") { + console.warn("GLSurface: callback parameter of captureFrame is deprecated, use the returned promise instead"); // eslint-disable-line no-console + promise.then(cb); + } + return promise; + } + onGLCaptureFrame = ({ nativeEvent: {frame} }) => { + this._pendingCaptureFrame.resolve(frame); + this._pendingCaptureFrame = undefined; } render () { - const { width, height, ...restProps } = this.props; + const { width, height, onLoad, onProgress, eventsThrough, ...restProps } = this.props; return ; } diff --git a/src/Surface.js b/src/Surface.js index 2141b2c..ae0c8f8 100644 --- a/src/Surface.js +++ b/src/Surface.js @@ -1,4 +1,6 @@ +const invariant = require("invariant"); const {createSurface} = require("gl-react"); +invariant(typeof createSurface === "function", "gl-react createSurface is not a function. Check your gl-react dependency"); const React = require("react-native"); const GLCanvas = require("./GLCanvas"); @@ -13,7 +15,7 @@ function renderVcontent (width, height, id, children, { visibleContent }) { left: 0, width: width, height: height, - overflow: "hidden" + overflow: "hidden", }; return {children}; } @@ -22,15 +24,17 @@ function renderVGL (props) { return ; } -function renderVcontainer ({ style, width, height }, contents, renderer) { +function renderVcontainer ({ style, width, height, visibleContent, eventsThrough }, contents, renderer) { const parentStyle = { position: "relative", ...style, width: width, height: height, - overflow: "hidden" + overflow: "hidden", }; - return + return {contents} {renderer} ; diff --git a/src/index.js b/src/index.js index b4aa06d..98ec5c7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,13 @@ +const invariant = require("invariant"); const { Shaders } = require("gl-react"); const Surface = require("./Surface"); const { NativeModules: { RNGLContext } } = require("react-native"); +invariant(RNGLContext, +`gl-react-native: the native module is not available. +Make sure you have properly configured it. +See README install instructions. + +React.NativeModules.RNGLContext is %s`, RNGLContext); // Hook Shaders to RNGLContext Shaders.list().map(id => RNGLContext.addShader(id, Shaders.get(id)));