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