diff --git a/android/rngl.iml b/android/rngl.iml new file mode 100644 index 0000000000000000000000000000000000000000..686ef627749ca7cfc9b593d3a04d650c0440292d --- /dev/null +++ b/android/rngl.iml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java b/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java index 54fd4a930c37e4df1773645cc010e2a1756d4de1..d39b9d430dfa0ccb22181dda35f30579051a2633 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLCanvas.java @@ -9,6 +9,7 @@ import android.opengl.GLSurfaceView; import android.util.DisplayMetrics; import android.view.ViewGroup; +import com.facebook.imagepipeline.core.ExecutorSupplier; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; @@ -28,13 +29,12 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.concurrent.Executor; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -public class GLCanvas - extends GLSurfaceView - implements GLSurfaceView.Renderer, RunInGLThread { +public class GLCanvas extends GLSurfaceView implements GLSurfaceView.Renderer, Executor { private ReactContext reactContext; private RNGLContext rnglContext; @@ -55,10 +55,13 @@ public class GLCanvas private Map shaders; private Map fbos; + private ExecutorSupplier executorSupplier; + private final Queue mRunOnDraw = new LinkedList<>(); - public GLCanvas(ThemedReactContext context) { + public GLCanvas(ThemedReactContext context, ExecutorSupplier executorSupplier) { super(context); reactContext = context; + this.executorSupplier = executorSupplier; rnglContext = context.getNativeModule(RNGLContext.class); setEGLContextClientVersion(2); @@ -110,7 +113,7 @@ public class GLCanvas if (contentTextures.size() != this.nbContentTextures) resizeUniformContentTextures(nbContentTextures); - syncEventsThrough(); // FIXME, really need to do this ? + syncEventsThrough(); // FIXME: need to do this here? if (!preloadingDone) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -219,8 +222,8 @@ public class GLCanvas // Sync methods - private final Queue mRunOnDraw = new LinkedList<>(); - public void runInGLThread (final Runnable runnable) { + @Override + public void execute (final Runnable runnable) { synchronized (mRunOnDraw) { mRunOnDraw.add(runnable); requestRender(); @@ -235,7 +238,7 @@ public class GLCanvas } public void requestSyncData () { - runInGLThread(new Runnable() { + execute(new Runnable() { public void run() { if (ensureCompiledShader(data)) syncData(); @@ -343,7 +346,7 @@ public class GLCanvas return resolveSrc(src); } - public GLRenderData recSyncData (GLData data, HashMap images) { + private GLRenderData recSyncData (GLData data, HashMap images) { Map prevImages = this.images; GLShader shader = getShader(data.shader); @@ -411,7 +414,7 @@ public class GLCanvas images.put(src, image); } if (image == null) { - image = new GLImage(reactContext.getApplicationContext(), this, new Runnable() { + image = new GLImage(this, executorSupplier.forDecode(), new Runnable() { public void run() { onImageLoad(src); } @@ -561,14 +564,14 @@ public class GLCanvas } } - public void syncData () { + private void syncData () { if (data == null) return; HashMap images = new HashMap<>(); renderData = recSyncData(data, images); this.images = images; } - public void recRender (GLRenderData renderData) { + private void recRender (GLRenderData renderData) { DisplayMetrics dm = reactContext.getResources().getDisplayMetrics(); int w = Float.valueOf(renderData.width.floatValue() * dm.density).intValue(); @@ -618,7 +621,7 @@ public class GLCanvas glDrawArrays(GL_TRIANGLES, 0, 6); } - public void render () { + private void render () { if (renderData == null) return; syncContentTextures(); @@ -631,13 +634,13 @@ public class GLCanvas glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); } - public void syncEventsThrough () { + private void syncEventsThrough () { // TODO: figure out how to do this... // For some reason, the click through is half working } - private void dispatchOnProgress(double progress, int count, int total) { + private void dispatchOnProgress (double progress, int count, int total) { WritableMap event = Arguments.createMap(); event.putDouble("progress", progress); event.putInt("count", count); @@ -649,7 +652,7 @@ public class GLCanvas event); } - public void dispatchOnLoad () { + private void dispatchOnLoad () { WritableMap event = Arguments.createMap(); ReactContext reactContext = (ReactContext)getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( diff --git a/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java b/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java index 341a9d7e9597c4d77dfc6c68efbe423aa32d4fb7..8a2cc48e2c74000b0784931ff683510d01fa5959 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLCanvasManager.java @@ -2,19 +2,23 @@ package com.projectseptember.RNGL; import android.support.annotation.Nullable; +import com.facebook.imagepipeline.core.DefaultExecutorSupplier; +import com.facebook.imagepipeline.core.ExecutorSupplier; +import com.facebook.imagepipeline.memory.PoolConfig; +import com.facebook.imagepipeline.memory.PoolFactory; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ReactProp; -import java.util.Locale; - public class GLCanvasManager extends SimpleViewManager { public static final String REACT_CLASS = "GLCanvas"; + private ExecutorSupplier executorSupplier; + @ReactProp(name="nbContentTextures") public void setNbContentTextures (GLCanvas view, int nbContentTextures) { view.setNbContentTextures(nbContentTextures); @@ -66,6 +70,11 @@ public class GLCanvasManager extends SimpleViewManager { @Override public GLCanvas createViewInstance (ThemedReactContext context) { - return new GLCanvas(context); + if (executorSupplier == null) { + PoolFactory poolFactory = new PoolFactory(PoolConfig.newBuilder().build()); + int numCpuBoundThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads(); + executorSupplier = new DefaultExecutorSupplier(numCpuBoundThreads); + } + return new GLCanvas(context, executorSupplier); } } diff --git a/android/src/main/java/com/projectseptember/RNGL/GLImage.java b/android/src/main/java/com/projectseptember/RNGL/GLImage.java index 39e641c9f7867667726c52a67aaee5d6f387e98d..2aa8f652959de675931df7730d5a2966c99a3ef6 100644 --- a/android/src/main/java/com/projectseptember/RNGL/GLImage.java +++ b/android/src/main/java/com/projectseptember/RNGL/GLImage.java @@ -1,75 +1,90 @@ package com.projectseptember.RNGL; import android.content.Context; -import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; -import android.os.AsyncTask; -import android.provider.MediaStore; import android.support.annotation.Nullable; import android.util.Log; +import com.facebook.common.references.CloseableReference; import com.facebook.common.util.UriUtil; +import com.facebook.datasource.DataSource; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; +import com.facebook.imagepipeline.image.CloseableImage; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; +import java.util.concurrent.Executor; /* This class is maintained and inspired from https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java -also inspired from -https://github.com/CyberAgent/android-gpuimage/blob/master/library/src/jp/co/cyberagent/android/gpuimage/GPUImage.java */ -public class GLImage { // TODO : we need to check support for local images - private final Context context; +public class GLImage { + private Uri src; private GLTexture texture; - - private boolean isDirty; - private AsyncTask task; - private Runnable onload; - private RunInGLThread glScheduler; - - public GLImage (Context context, RunInGLThread glScheduler, Runnable onload) { - this.context = context; - this.onload = onload; - this.glScheduler = glScheduler; + private Runnable onLoad; + private Executor glExecutor; + private Executor decodeExecutor; + private DataSource> pending; + + public GLImage (Executor glExecutor, Executor decodeExecutor, Runnable onLoad) { + this.onLoad = onLoad; + this.glExecutor = glExecutor; + this.decodeExecutor = decodeExecutor; this.texture = new GLTexture(); } - public void setSrc(Uri src) { - if (this.src == src) return; + public void setSrc (Uri src) { + if (this.src == src || this.src!=null && this.src.equals(src)) return; this.src = src; reloadImage(); } private void reloadImage () { - isDirty = true; + if (pending != null && !pending.isFinished()) + pending.close(); + + final Uri uri = src; + ImageRequest imageRequest = ImageRequestBuilder + .newBuilderWithSource(uri) + .build(); + + pending = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null); + + pending.subscribe(new BaseBitmapDataSubscriber() { + @Override + protected void onNewResultImpl(@Nullable Bitmap bitmap) { + onLoad(bitmap); + } + @Override + protected void onFailureImpl(DataSource> dataSource) { + Log.e("GLImage", "Failed to load '" + uri.getPath() + "'", dataSource.getFailureCause()); + } + }, decodeExecutor); } - public void onLoad (final Bitmap bitmap) { - glScheduler.runInGLThread(new Runnable() { + public void onLoad (final Bitmap source) { + Matrix matrix = new Matrix(); + matrix.postScale(1, -1); + final Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true); + glExecutor.execute(new Runnable() { public void run() { - Log.i("GLImage", "loaded="+src.getPath()); texture.setPixels(bitmap); - onload.run(); + bitmap.recycle(); + onLoad.run(); } }); } public GLTexture getTexture() { - if (isDirty) { - if (task != null) task.cancel(true); - task = new LoadImageUriTask(this, src).execute(); - isDirty = false; - } return texture; } - public static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) { + public static @Nullable Uri getResourceDrawableUri (Context context, @Nullable String name) { if (name == null || name.isEmpty()) { return null; } @@ -83,104 +98,4 @@ public class GLImage { // TODO : we need to check support for local images .path(String.valueOf(resId)) .build(); } - - - private static class LoadImageUriTask extends LoadImageTask { - - private final Uri mUri; - - public LoadImageUriTask(GLImage gpuImage, Uri uri) { - super(gpuImage); - mUri = uri; - } - - @Override - protected Bitmap decode(BitmapFactory.Options options) { - Log.i("GLImage", "loading...="+mUri.getPath()); - // FIXME: image loading is very long (probably decoding)... possible to re-use some React Native work ? - try { - InputStream inputStream; - if (mUri.getScheme().startsWith("http") || mUri.getScheme().startsWith("https")) { - inputStream = new URL(mUri.toString()).openStream(); - } else { - inputStream = glImage.context.getContentResolver().openInputStream(mUri); - } - return BitmapFactory.decodeStream(inputStream, null, options); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected int getImageOrientation() throws IOException { - Cursor cursor = glImage.context.getContentResolver().query(mUri, - new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); - - if (cursor == null || cursor.getCount() != 1) { - return 0; - } - - cursor.moveToFirst(); - int orientation = cursor.getInt(0); - cursor.close(); - return orientation; - } - } - - private static abstract class LoadImageTask extends AsyncTask { - - protected GLImage glImage; - - public LoadImageTask (GLImage glImage) { - this.glImage = glImage; - } - - @Override - protected Bitmap doInBackground(Void... params) { - return loadResizedImage(); - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - super.onPostExecute(bitmap); - glImage.onLoad(bitmap); - } - - protected abstract Bitmap decode(BitmapFactory.Options options); - - private Bitmap loadResizedImage() { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - decode(options); - options = new BitmapFactory.Options(); - options.inPreferredConfig = Bitmap.Config.RGB_565; - options.inTempStorage = new byte[32 * 1024]; - Bitmap bitmap = decode(options); - if (bitmap == null) { - return null; - } - - Bitmap transformedBitmap; - Matrix matrix = new Matrix(); - - try { - int orientation = getImageOrientation(); - if (orientation != 0) { - matrix.postRotate(orientation); - } - } catch (IOException e) { - e.printStackTrace(); - } - - matrix.postScale(1, -1); - - transformedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - bitmap.recycle(); - - return transformedBitmap; - } - - protected abstract int getImageOrientation() throws IOException; - } } diff --git a/android/src/main/java/com/projectseptember/RNGL/RunInGLThread.java b/android/src/main/java/com/projectseptember/RNGL/RunInGLThread.java deleted file mode 100644 index 7cadfa8de65f7c35fe22349f955707959ee53f41..0000000000000000000000000000000000000000 --- a/android/src/main/java/com/projectseptember/RNGL/RunInGLThread.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.projectseptember.RNGL; - -public interface RunInGLThread { - void runInGLThread(final Runnable runnable); -}