| /* |
| * Copyright 2018 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| package org.skia.skottie; |
| |
| import android.graphics.SurfaceTexture; |
| import android.graphics.drawable.Animatable; |
| import android.opengl.GLUtils; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.util.Log; |
| import android.view.SurfaceView; |
| import android.view.TextureView; |
| |
| import java.io.InputStream; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| import javax.microedition.khronos.egl.EGL10; |
| import javax.microedition.khronos.egl.EGLConfig; |
| import javax.microedition.khronos.egl.EGLContext; |
| import javax.microedition.khronos.egl.EGLDisplay; |
| import javax.microedition.khronos.egl.EGLSurface; |
| |
| class SkottieRunner { |
| private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; |
| private static final int EGL_OPENGL_ES2_BIT = 4; |
| private static final int STENCIL_BUFFER_SIZE = 8; |
| private static final long TIME_OUT_MS = 10000; |
| private static final String LOG_TAG = "SkottiePlayer"; |
| |
| private static SkottieRunner sInstance; |
| |
| private HandlerThread mGLThreadLooper; |
| private Handler mGLThread; |
| EGL10 mEgl; |
| EGLDisplay mEglDisplay; |
| EGLConfig mEglConfig; |
| EGLContext mEglContext; |
| EGLSurface mPBufferSurface; |
| private long mNativeProxy; |
| |
| static { |
| System.loadLibrary("skottie_android"); |
| } |
| /** |
| * Gets SkottieRunner singleton instance. |
| */ |
| public static synchronized SkottieRunner getInstance() { |
| if (sInstance == null) { |
| sInstance = new SkottieRunner(); |
| } |
| return sInstance; |
| } |
| |
| /** |
| * Create a new animation by feeding data from "is" and replaying in a TextureView. |
| * TextureView is tracked internally for SurfaceTexture state. |
| */ |
| public SkottieAnimation createAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount) { |
| return new SkottieAnimation(view, is, backgroundColor, repeatCount); |
| } |
| |
| /** |
| * Create a new animation by feeding data from "is" and replaying in a SurfaceTexture. |
| * SurfaceTexture is possibly taken from a TextureView and can be updated with |
| * updateAnimationSurface. |
| */ |
| public SkottieAnimation createAnimation(SurfaceTexture surfaceTexture, InputStream is) { |
| return new SkottieAnimation(surfaceTexture, is); |
| } |
| |
| /** |
| * Create a new animation by feeding data from "is" and replaying in a SurfaceView. |
| * State is controlled internally by SurfaceHolder. |
| */ |
| public SkottieAnimation createAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount) { |
| return new SkottieAnimation(view, is, backgroundColor, repeatCount); |
| } |
| |
| /** |
| * Pass a new SurfaceTexture: use this method only if managing TextureView outside |
| * SkottieRunner. |
| */ |
| public void updateAnimationSurface(Animatable animation, SurfaceTexture surfaceTexture, |
| int width, int height) { |
| try { |
| runOnGLThread(() -> { |
| ((SkottieAnimation) animation).setSurfaceTexture(surfaceTexture); |
| ((SkottieAnimation) animation).updateSurface(width, height); |
| }); |
| } |
| catch (Throwable t) { |
| Log.e(LOG_TAG, "update failed", t); |
| throw new RuntimeException(t); |
| } |
| } |
| |
| private SkottieRunner() |
| { |
| mGLThreadLooper = new HandlerThread("SkottieAnimator"); |
| mGLThreadLooper.start(); |
| mGLThread = new Handler(mGLThreadLooper.getLooper()); |
| initGl(); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| runOnGLThread(this::doFinishGL); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| long getNativeProxy() { return mNativeProxy; } |
| |
| private class RunSignalAndCatch implements Runnable { |
| public Throwable error; |
| private Runnable mRunnable; |
| private CountDownLatch mFence; |
| |
| RunSignalAndCatch(Runnable run, CountDownLatch fence) { |
| mRunnable = run; |
| mFence = fence; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| mRunnable.run(); |
| } catch (Throwable t) { |
| error = t; |
| } finally { |
| mFence.countDown(); |
| } |
| } |
| } |
| |
| void runOnGLThread(Runnable r) throws Throwable { |
| runOnGLThread(r, false); |
| } |
| |
| private void runOnGLThread(Runnable r, boolean postAtFront) throws Throwable { |
| |
| CountDownLatch fence = new CountDownLatch(1); |
| RunSignalAndCatch wrapper = new RunSignalAndCatch(r, fence); |
| if (postAtFront) { |
| mGLThread.postAtFrontOfQueue(wrapper); |
| } else { |
| mGLThread.post(wrapper); |
| } |
| if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) { |
| throw new TimeoutException(); |
| } |
| if (wrapper.error != null) { |
| throw wrapper.error; |
| } |
| } |
| |
| private void initGl() |
| { |
| try { |
| runOnGLThread(mDoInitGL); |
| } |
| catch (Throwable t) { |
| Log.e(LOG_TAG, "initGl failed", t); |
| throw new RuntimeException(t); |
| } |
| } |
| |
| private Runnable mDoInitGL = new Runnable() { |
| @Override |
| public void run() { |
| mEgl = (EGL10) EGLContext.getEGL(); |
| |
| mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); |
| if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { |
| throw new RuntimeException("eglGetDisplay failed " |
| + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } |
| |
| int[] version = new int[2]; |
| if (!mEgl.eglInitialize(mEglDisplay, version)) { |
| throw new RuntimeException("eglInitialize failed " + |
| GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } |
| |
| mEglConfig = chooseEglConfig(); |
| if (mEglConfig == null) { |
| throw new RuntimeException("eglConfig not initialized"); |
| } |
| |
| mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); |
| |
| int[] attribs = new int[] { |
| EGL10.EGL_WIDTH, 1, |
| EGL10.EGL_HEIGHT, 1, |
| EGL10.EGL_NONE |
| }; |
| |
| mPBufferSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); |
| if (mPBufferSurface == null || mPBufferSurface == EGL10.EGL_NO_SURFACE) { |
| int error = mEgl.eglGetError(); |
| throw new RuntimeException("createPbufferSurface failed " |
| + GLUtils.getEGLErrorString(error)); |
| } |
| |
| if (!mEgl.eglMakeCurrent(mEglDisplay, mPBufferSurface, mPBufferSurface, mEglContext)) { |
| throw new RuntimeException("eglMakeCurrent failed " |
| + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } |
| |
| mNativeProxy = nCreateProxy(); |
| } |
| }; |
| |
| EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { |
| int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; |
| return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); |
| } |
| |
| private EGLConfig chooseEglConfig() { |
| int[] configsCount = new int[1]; |
| EGLConfig[] configs = new EGLConfig[1]; |
| int[] configSpec = getConfig(); |
| if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { |
| throw new IllegalArgumentException("eglChooseConfig failed " + |
| GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } else if (configsCount[0] > 0) { |
| return configs[0]; |
| } |
| return null; |
| } |
| |
| private int[] getConfig() { |
| return new int[] { |
| EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| EGL10.EGL_RED_SIZE, 8, |
| EGL10.EGL_GREEN_SIZE, 8, |
| EGL10.EGL_BLUE_SIZE, 8, |
| EGL10.EGL_ALPHA_SIZE, 8, |
| EGL10.EGL_DEPTH_SIZE, 0, |
| EGL10.EGL_STENCIL_SIZE, STENCIL_BUFFER_SIZE, |
| EGL10.EGL_NONE |
| }; |
| } |
| |
| private void doFinishGL() { |
| nDeleteProxy(mNativeProxy); |
| mNativeProxy = 0; |
| if (mEglDisplay != null) { |
| if (mEglContext != null) { |
| mEgl.eglDestroyContext(mEglDisplay, mEglContext); |
| mEglContext = null; |
| } |
| if (mPBufferSurface != null) { |
| mEgl.eglDestroySurface(mEglDisplay, mPBufferSurface); |
| mPBufferSurface = null; |
| } |
| |
| mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, |
| EGL10.EGL_NO_CONTEXT); |
| |
| mEgl.eglTerminate(mEglDisplay); |
| mEglDisplay = null; |
| } |
| } |
| |
| private static native long nCreateProxy(); |
| private static native void nDeleteProxy(long nativeProxy); |
| } |
| |