• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 package org.skia.skottie;
9 
10 import android.graphics.SurfaceTexture;
11 import android.graphics.drawable.Animatable;
12 import android.opengl.GLUtils;
13 import android.os.Handler;
14 import android.os.HandlerThread;
15 import android.util.Log;
16 import android.view.SurfaceView;
17 import android.view.TextureView;
18 
19 import java.io.InputStream;
20 import java.util.concurrent.CountDownLatch;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23 
24 import javax.microedition.khronos.egl.EGL10;
25 import javax.microedition.khronos.egl.EGLConfig;
26 import javax.microedition.khronos.egl.EGLContext;
27 import javax.microedition.khronos.egl.EGLDisplay;
28 import javax.microedition.khronos.egl.EGLSurface;
29 
30 class SkottieRunner {
31     private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
32     private static final int EGL_OPENGL_ES2_BIT = 4;
33     private static final int STENCIL_BUFFER_SIZE = 8;
34     private static final long TIME_OUT_MS = 10000;
35     private static final String LOG_TAG = "SkottiePlayer";
36 
37     private static SkottieRunner sInstance;
38 
39     private HandlerThread mGLThreadLooper;
40     private Handler mGLThread;
41     EGL10 mEgl;
42     EGLDisplay mEglDisplay;
43     EGLConfig mEglConfig;
44     EGLContext mEglContext;
45     EGLSurface mPBufferSurface;
46     private long mNativeProxy;
47 
48     static {
49         System.loadLibrary("skottie_android");
50     }
51     /**
52      * Gets SkottieRunner singleton instance.
53      */
getInstance()54     public static synchronized SkottieRunner getInstance() {
55         if (sInstance == null) {
56             sInstance = new SkottieRunner();
57         }
58         return sInstance;
59     }
60 
61     /**
62      * Create a new animation by feeding data from "is" and replaying in a TextureView.
63      * TextureView is tracked internally for SurfaceTexture state.
64      */
createAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount)65     public SkottieAnimation createAnimation(TextureView view, InputStream is, int backgroundColor, int repeatCount) {
66         return new SkottieAnimation(view, is, backgroundColor, repeatCount);
67     }
68 
69     /**
70      * Create a new animation by feeding data from "is" and replaying in a SurfaceTexture.
71      * SurfaceTexture is possibly taken from a TextureView and can be updated with
72      * updateAnimationSurface.
73      */
createAnimation(SurfaceTexture surfaceTexture, InputStream is)74     public SkottieAnimation createAnimation(SurfaceTexture surfaceTexture, InputStream is) {
75         return new SkottieAnimation(surfaceTexture, is);
76     }
77 
78     /**
79      * Create a new animation by feeding data from "is" and replaying in a SurfaceView.
80      * State is controlled internally by SurfaceHolder.
81      */
createAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount)82     public SkottieAnimation createAnimation(SurfaceView view, InputStream is, int backgroundColor, int repeatCount) {
83         return new SkottieAnimation(view, is, backgroundColor, repeatCount);
84     }
85 
86     /**
87      * Pass a new SurfaceTexture: use this method only if managing TextureView outside
88      * SkottieRunner.
89      */
updateAnimationSurface(Animatable animation, SurfaceTexture surfaceTexture, int width, int height)90     public void updateAnimationSurface(Animatable animation, SurfaceTexture surfaceTexture,
91                                        int width, int height) {
92         try {
93             runOnGLThread(() -> {
94                 ((SkottieAnimation) animation).setSurfaceTexture(surfaceTexture);
95                 ((SkottieAnimation) animation).updateSurface(width, height);
96             });
97         }
98         catch (Throwable t) {
99             Log.e(LOG_TAG, "update failed", t);
100             throw new RuntimeException(t);
101         }
102     }
103 
SkottieRunner()104     private SkottieRunner()
105     {
106         mGLThreadLooper = new HandlerThread("SkottieAnimator");
107         mGLThreadLooper.start();
108         mGLThread = new Handler(mGLThreadLooper.getLooper());
109         initGl();
110     }
111 
112     @Override
finalize()113     protected void finalize() throws Throwable {
114         try {
115             runOnGLThread(this::doFinishGL);
116         } finally {
117             super.finalize();
118         }
119     }
120 
getNativeProxy()121     long getNativeProxy() { return mNativeProxy; }
122 
123     private class RunSignalAndCatch implements Runnable {
124         public Throwable error;
125         private Runnable mRunnable;
126         private CountDownLatch mFence;
127 
RunSignalAndCatch(Runnable run, CountDownLatch fence)128         RunSignalAndCatch(Runnable run, CountDownLatch fence) {
129             mRunnable = run;
130             mFence = fence;
131         }
132 
133         @Override
run()134         public void run() {
135             try {
136                 mRunnable.run();
137             } catch (Throwable t) {
138                 error = t;
139             } finally {
140                 mFence.countDown();
141             }
142         }
143     }
144 
runOnGLThread(Runnable r)145     void runOnGLThread(Runnable r) throws Throwable {
146         runOnGLThread(r, false);
147     }
148 
runOnGLThread(Runnable r, boolean postAtFront)149     private void runOnGLThread(Runnable r, boolean postAtFront) throws Throwable {
150 
151         CountDownLatch fence = new CountDownLatch(1);
152         RunSignalAndCatch wrapper = new RunSignalAndCatch(r, fence);
153         if (postAtFront) {
154             mGLThread.postAtFrontOfQueue(wrapper);
155         } else {
156             mGLThread.post(wrapper);
157         }
158         if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
159             throw new TimeoutException();
160         }
161         if (wrapper.error != null) {
162             throw wrapper.error;
163         }
164     }
165 
initGl()166     private void initGl()
167     {
168         try {
169             runOnGLThread(mDoInitGL);
170         }
171         catch (Throwable t) {
172             Log.e(LOG_TAG, "initGl failed", t);
173             throw new RuntimeException(t);
174         }
175     }
176 
177     private Runnable mDoInitGL = new Runnable() {
178         @Override
179         public void run() {
180             mEgl = (EGL10) EGLContext.getEGL();
181 
182             mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
183             if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
184                 throw new RuntimeException("eglGetDisplay failed "
185                         + GLUtils.getEGLErrorString(mEgl.eglGetError()));
186             }
187 
188             int[] version = new int[2];
189             if (!mEgl.eglInitialize(mEglDisplay, version)) {
190                 throw new RuntimeException("eglInitialize failed " +
191                         GLUtils.getEGLErrorString(mEgl.eglGetError()));
192             }
193 
194             mEglConfig = chooseEglConfig();
195             if (mEglConfig == null) {
196                 throw new RuntimeException("eglConfig not initialized");
197             }
198 
199             mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
200 
201             int[] attribs = new int[] {
202                     EGL10.EGL_WIDTH, 1,
203                     EGL10.EGL_HEIGHT, 1,
204                     EGL10.EGL_NONE
205             };
206 
207             mPBufferSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
208             if (mPBufferSurface == null || mPBufferSurface == EGL10.EGL_NO_SURFACE) {
209                 int error = mEgl.eglGetError();
210                 throw new RuntimeException("createPbufferSurface failed "
211                         + GLUtils.getEGLErrorString(error));
212             }
213 
214             if (!mEgl.eglMakeCurrent(mEglDisplay, mPBufferSurface, mPBufferSurface, mEglContext)) {
215                 throw new RuntimeException("eglMakeCurrent failed "
216                         + GLUtils.getEGLErrorString(mEgl.eglGetError()));
217             }
218 
219             mNativeProxy = nCreateProxy();
220         }
221     };
222 
createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig)223     EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
224         int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
225         return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
226     }
227 
chooseEglConfig()228     private EGLConfig chooseEglConfig() {
229         int[] configsCount = new int[1];
230         EGLConfig[] configs = new EGLConfig[1];
231         int[] configSpec = getConfig();
232         if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
233             throw new IllegalArgumentException("eglChooseConfig failed " +
234                     GLUtils.getEGLErrorString(mEgl.eglGetError()));
235         } else if (configsCount[0] > 0) {
236             return configs[0];
237         }
238         return null;
239     }
240 
getConfig()241     private int[] getConfig() {
242         return new int[] {
243                 EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
244                 EGL10.EGL_RED_SIZE, 8,
245                 EGL10.EGL_GREEN_SIZE, 8,
246                 EGL10.EGL_BLUE_SIZE, 8,
247                 EGL10.EGL_ALPHA_SIZE, 8,
248                 EGL10.EGL_DEPTH_SIZE, 0,
249                 EGL10.EGL_STENCIL_SIZE, STENCIL_BUFFER_SIZE,
250                 EGL10.EGL_NONE
251         };
252     }
253 
doFinishGL()254     private void doFinishGL() {
255         nDeleteProxy(mNativeProxy);
256         mNativeProxy = 0;
257         if (mEglDisplay != null) {
258             if (mEglContext != null) {
259                 mEgl.eglDestroyContext(mEglDisplay, mEglContext);
260                 mEglContext = null;
261             }
262             if (mPBufferSurface != null) {
263                 mEgl.eglDestroySurface(mEglDisplay, mPBufferSurface);
264                 mPBufferSurface = null;
265             }
266 
267             mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,  EGL10.EGL_NO_SURFACE,
268                     EGL10.EGL_NO_CONTEXT);
269 
270             mEgl.eglTerminate(mEglDisplay);
271             mEglDisplay = null;
272         }
273     }
274 
nCreateProxy()275     private static native long nCreateProxy();
nDeleteProxy(long nativeProxy)276     private static native void nDeleteProxy(long nativeProxy);
277 }
278 
279