Inline hippo's glcomponents

* Replace android support with androidx as well
This commit is contained in:
tarsin committed 2022-05-30 09:20:02 +08:00
1 parent cf3769959f
commit 01daf4a63e
68 files changed
+16325 -7

No files matched your search

@@ -0,0 +1,751 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.opengl.GLUtils;
import android.os.Build;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import androidx.annotation.NonNull;
import com.hippo.glview.anim.CanvasAnimation;
import com.hippo.glview.glrenderer.BasicTexture;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.glrenderer.GLES11Canvas;
import com.hippo.glview.glrenderer.GLES20Canvas;
import com.hippo.glview.glrenderer.UploadedTexture;
import com.hippo.glview.util.ApiHelper;
import com.hippo.glview.util.GalleryUtils;
import com.hippo.glview.util.MotionEventHelper;
import com.hippo.tuxiang.EGLConfigChooser;
import com.hippo.tuxiang.EGLContextFactory;
import com.hippo.tuxiang.GLSurfaceView;
import com.hippo.tuxiang.Renderer;
import com.hippo.yorozuya.AssertUtils;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
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.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
// TODO Call attachToRoot and detachFromRoot in render thread
// The root component of all <code>GLView</code>s. The rendering is done in GL
// thread while the event handling is done in the main thread. To synchronize
// the two threads, the entry points of this package need to synchronize on the
// <code>GLRootView</code> instance unless it can be proved that the rendering
// thread won't access the same thing as the method. The entry points include:
// (1) The public methods of HeadUpDisplay
// (2) The public methods of CameraHeadUpDisplay
// (3) The overridden methods in GLRootView.
public class GLRootView extends GLSurfaceView
implements GLRoot {
private static final String TAG = "GLRootView";
private static final boolean DEBUG_FPS = false;
private int mFrameCount = 0;
private long mFrameCountingStart = 0;
private static final boolean DEBUG_INVALIDATE = false;
private int mInvalidateColor = 0;
private static final boolean DEBUG_DRAWING_STAT = false;
private static final int FLAG_INITIALIZED = 1;
private static final int FLAG_NEED_LAYOUT = 2;
private GL11 mGL;
private GLCanvas mCanvas;
private GLView mContentView;
private OrientationSource mOrientationSource;
// mCompensation is the difference between the UI orientation on GLCanvas
// and the framework orientation. See OrientationManager for details.
private int mCompensation;
// mCompensationMatrix maps the coordinates of touch events. It is kept sync
// with mCompensation.
private final Matrix mCompensationMatrix = new Matrix();
private int mDisplayRotation;
private int mFlags = FLAG_NEED_LAYOUT;
private volatile boolean mRenderRequested = false;
private final ArrayList<CanvasAnimation> mAnimations =
new ArrayList<>();
private final ArrayDeque<OnGLIdleListener> mIdleListeners =
new ArrayDeque<>();
private final IdleRunner mIdleRunner = new IdleRunner();
private final ReentrantLock mRenderLock = new ReentrantLock();
private final Condition mFreezeCondition =
mRenderLock.newCondition();
private boolean mFreeze;
private boolean mInDownState = false;
private int mEGLContextClientVersion;
private class GLRootRenderer implements Renderer {
/**
* Called when the context is created, possibly after automatic destruction.
*/
@Override
public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
GL11 gl = (GL11) gl1;
if (mGL != null) {
// The GL Object has changed
Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
}
mRenderLock.lock();
try {
mGL = gl;
mCanvas = mEGLContextClientVersion == 2 ? new GLES20Canvas() : new GLES11Canvas(gl);
BasicTexture.invalidateAllTextures();
} finally {
mRenderLock.unlock();
}
if (DEBUG_FPS) {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
} else {
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
/**
* Called when the OpenGL surface is recreated without destroying the
* context.
*/
@Override
public void onSurfaceChanged(GL10 gl1, int width, int height) {
Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString());
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
GalleryUtils.setRenderThread();
GL11 gl = (GL11) gl1;
AssertUtils.assertTrue(mGL == gl);
mCanvas.setSize(width, height);
}
@Override
public boolean onDrawFrame(GL10 gl) {
AnimationTime.update();
long t0 = System.nanoTime();
mRenderLock.lock();
while (mFreeze) {
mFreezeCondition.awaitUninterruptibly();
}
try {
onDrawFrameLocked(gl);
} finally {
mRenderLock.unlock();
}
long t = System.nanoTime();
long duration = (t - t0) / 1000000;
if (duration > 8) {
Log.v(TAG, "--- " + duration + " ---");
}
return true;
}
@Override
public void onGLThreadStart() { }
@Override
public void onGLThreadExit() {
if (mContentView != null && mContentView.isAttachedToRoot()) {
mContentView.detachFromRoot();
}
}
@Override
public void onGLThreadPause() {
if (mContentView != null) {
mContentView.pause();
}
}
@Override
public void onGLThreadResume() {
if (mContentView != null) {
mContentView.resume();
}
}
}
public GLRootView(Context context) {
this(context, null);
}
@SuppressWarnings("deprecation")
public GLRootView(Context context, AttributeSet attrs) {
super(context, attrs);
mFlags |= FLAG_INITIALIZED;
setBackgroundDrawable(null);
setEGLConfigChooser(new ConfigChooser());
setEGLContextFactory(new ContextFactory());
setRenderer(new GLRootRenderer());
getHolder().setFormat(PixelFormat.RGB_888);
// Uncomment this to enable gl error check.
// setDebugFlags(DEBUG_CHECK_GL_ERROR);
}
@Override
public void registerLaunchedAnimation(CanvasAnimation animation) {
// Register the newly launched animation so that we can set the start
// time more precisely. (Usually, it takes much longer for first
// rendering, so we set the animation start time as the time we
// complete rendering)
mAnimations.add(animation);
}
@Override
public void addOnGLIdleListener(OnGLIdleListener listener) {
synchronized (mIdleListeners) {
mIdleListeners.addLast(listener);
if (mCanvas != null) {
// Wait for onSurfaceCreated
mIdleRunner.enable();
}
}
}
@Override
public void setContentPane(GLView content) {
if (mContentView == content) return;
if (mContentView != null) {
if (mInDownState) {
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mContentView.dispatchTouchEvent(cancelEvent);
cancelEvent.recycle();
mInDownState = false;
}
mContentView.detachFromRoot();
BasicTexture.yieldAllTextures();
}
mContentView = content;
if (content != null) {
content.attachToRoot(this);
requestLayoutContentPane();
}
}
@Override
public void requestRenderForced() {
superRequestRender();
}
@Override
public void requestRender() {
if (DEBUG_INVALIDATE) {
StackTraceElement e = Thread.currentThread().getStackTrace()[4];
String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
Log.d(TAG, "invalidate: " + caller);
}
if (mRenderRequested) return;
mRenderRequested = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postOnAnimation(mRequestRenderOnAnimationFrame);
} else {
super.requestRender();
}
}
private final Runnable mRequestRenderOnAnimationFrame = new Runnable() {
@Override
public void run() {
superRequestRender();
}
};
private void superRequestRender() {
super.requestRender();
}
@Override
public void requestLayoutContentPane() {
mRenderLock.lock();
try {
if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
// "View" system will invoke onLayout() for initialization(bug ?), we
// have to ignore it since the GLThread is not ready yet.
if ((mFlags & FLAG_INITIALIZED) == 0) return;
mFlags |= FLAG_NEED_LAYOUT;
requestRender();
} finally {
mRenderLock.unlock();
}
}
private void layoutContentPane() {
mFlags &= ~FLAG_NEED_LAYOUT;
int w = getWidth();
int h = getHeight();
int displayRotation;
int compensation;
// Get the new orientation values
if (mOrientationSource != null) {
displayRotation = mOrientationSource.getDisplayRotation();
compensation = mOrientationSource.getCompensation();
} else {
displayRotation = 0;
compensation = 0;
}
if (mCompensation != compensation) {
mCompensation = compensation;
if (mCompensation % 180 != 0) {
mCompensationMatrix.setRotate(mCompensation);
// move center to origin before rotation
mCompensationMatrix.preTranslate(-w / 2, -h / 2);
// align with the new origin after rotation
mCompensationMatrix.postTranslate(h / 2, w / 2);
} else {
mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
}
}
mDisplayRotation = displayRotation;
// Do the actual layout.
if (mCompensation % 180 != 0) {
int tmp = w;
w = h;
h = tmp;
}
Log.i(TAG, "layout content pane " + w + "x" + h + " (compensation " + mCompensation + ")");
if (mContentView != null && w != 0 && h != 0) {
mContentView.measure(GLView.MeasureSpec.makeMeasureSpec(w, GLView.MeasureSpec.EXACTLY),
GLView.MeasureSpec.makeMeasureSpec(h, GLView.MeasureSpec.EXACTLY));
mContentView.layout(0, 0, w, h);
}
// Uncomment this to dump the view hierarchy.
//mContentView.dumpTree("");
}
@Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
if (changed) requestLayoutContentPane();
}
private void outputFps() {
long now = System.nanoTime();
if (mFrameCountingStart == 0) {
mFrameCountingStart = now;
} else if ((now - mFrameCountingStart) > 1000000000) {
Log.d(TAG, "fps: " + (double) mFrameCount
* 1000000000 / (now - mFrameCountingStart));
mFrameCountingStart = now;
mFrameCount = 0;
}
++mFrameCount;
}
private void onDrawFrameLocked(GL10 gl) {
if (DEBUG_FPS) outputFps();
// release the unbound textures and deleted buffers.
mCanvas.deleteRecycledResources();
// reset texture upload limit
UploadedTexture.resetUploadLimit();
mRenderRequested = false;
if ((mOrientationSource != null
&& mDisplayRotation != mOrientationSource.getDisplayRotation())
|| (mFlags & FLAG_NEED_LAYOUT) != 0) {
layoutContentPane();
}
mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
rotateCanvas(-mCompensation);
if (mContentView != null) {
mContentView.render(mCanvas);
} else {
// Make sure we always draw something to prevent displaying garbage
mCanvas.clearBuffer();
}
mCanvas.restore();
if (!mAnimations.isEmpty()) {
long now = AnimationTime.get();
for (int i = 0, n = mAnimations.size(); i < n; i++) {
mAnimations.get(i).setStartTime(now);
}
mAnimations.clear();
}
if (UploadedTexture.uploadLimitReached()) {
requestRender();
}
synchronized (mIdleListeners) {
if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
}
if (DEBUG_INVALIDATE) {
mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
mInvalidateColor = ~mInvalidateColor;
}
if (DEBUG_DRAWING_STAT) {
mCanvas.dumpStatisticsAndClear();
}
}
private void rotateCanvas(int degrees) {
if (degrees == 0) return;
int w = getWidth();
int h = getHeight();
int cx = w / 2;
int cy = h / 2;
mCanvas.translate(cx, cy);
mCanvas.rotate(degrees, 0, 0, 1);
if (degrees % 180 != 0) {
mCanvas.translate(-cy, -cx);
} else {
mCanvas.translate(-cx, -cy);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (!isEnabled()) return false;
int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
mInDownState = false;
} else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
return false;
}
if (mCompensation != 0) {
event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
}
mRenderLock.lock();
try {
// If this has been detached from root, we don't need to handle event
boolean handled = mContentView != null
&& mContentView.dispatchTouchEvent(event);
if (action == MotionEvent.ACTION_DOWN && handled) {
mInDownState = true;
}
return handled;
} finally {
mRenderLock.unlock();
}
}
private class IdleRunner implements Runnable {
// true if the idle runner is in the queue
private boolean mActive = false;
@Override
public void run() {
OnGLIdleListener listener;
synchronized (mIdleListeners) {
mActive = false;
if (mIdleListeners.isEmpty()) return;
listener = mIdleListeners.removeFirst();
}
mRenderLock.lock();
boolean keepInQueue;
try {
keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
} finally {
mRenderLock.unlock();
}
synchronized (mIdleListeners) {
if (keepInQueue) mIdleListeners.addLast(listener);
if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
}
}
public void enable() {
// Who gets the flag can add it to the queue
if (mActive) return;
mActive = true;
queueEvent(this);
}
}
@Override
public void lockRenderThread() {
mRenderLock.lock();
}
@Override
public void unlockRenderThread() {
mRenderLock.unlock();
}
@Override
public void onPause() {
unfreeze();
super.onPause();
}
@Override
public void setOrientationSource(OrientationSource source) {
mOrientationSource = source;
}
@Override
public int getDisplayRotation() {
return mDisplayRotation;
}
@Override
public int getCompensation() {
return mCompensation;
}
@Override
public Matrix getCompensationMatrix() {
return mCompensationMatrix;
}
@Override
public void freeze() {
mRenderLock.lock();
mFreeze = true;
mRenderLock.unlock();
}
@Override
public void unfreeze() {
mRenderLock.lock();
mFreeze = false;
mFreezeCondition.signalAll();
mRenderLock.unlock();
}
@Override
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void setLightsOutMode(boolean enabled) {
if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
int flags = 0;
if (enabled) {
flags = STATUS_BAR_HIDDEN;
if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
setSystemUiVisibility(flags);
}
// We need to unfreeze in the following methods and in onPause().
// These methods will wait on GLThread. If we have freezed the GLRootView,
// the GLThread will wait on main thread to call unfreeze and cause dead
// lock.
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
unfreeze();
super.surfaceChanged(holder, format, w, h);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
unfreeze();
super.surfaceCreated(holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
unfreeze();
super.surfaceDestroyed(holder);
}
@Override
protected void onDetachedFromWindow() {
unfreeze();
super.onDetachedFromWindow();
}
/**
* {@inheritDoc}
*/
@Override
protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
if (mContentView != null) {
mContentView.saveHierarchyState(container);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
if (mContentView != null) {
mContentView.restoreHierarchyState(container);
}
}
@Override
protected void finalize() throws Throwable {
try {
unfreeze();
} finally {
super.finalize();
}
}
// Always chose a config
private class ConfigChooser implements EGLConfigChooser {
private final int[] mValue = new int[1];
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] num_config = new int[1];
int[] configSpec = new int[] { EGL10.EGL_NONE };
if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) {
throw new IllegalArgumentException("eglChooseConfig failed");
}
int numConfigs = num_config[0];
if (numConfigs <= 0) {
throw new IllegalArgumentException(
"No configs match configSpec");
}
EGLConfig[] configs = new EGLConfig[numConfigs];
if (!egl.eglChooseConfig(display, configSpec, configs, numConfigs, num_config)) {
throw new IllegalArgumentException("eglChooseConfig#2 failed");
}
EGLConfig config = chooseConfig(egl, display, configs);
if (config == null) {
throw new IllegalArgumentException("No config chosen");
}
int renderableType = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE, 0);
if ((renderableType & 0x0004) == 0x0004 /* EGL_OPENGL_ES2_BIT */) {
mEGLContextClientVersion = 2;
} else {
mEGLContextClientVersion = 1;
}
return config;
}
private EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
// Use score to avoid "No config chosen"
int configIndex = 0;
int maxScore = 0;
for (int i = 0, n = configs.length; i < n; i++) {
final EGLConfig config = configs[i];
final int redSize = findConfigAttrib(egl, display, config,
EGL10.EGL_RED_SIZE, 0);
final int greenSize = findConfigAttrib(egl, display, config,
EGL10.EGL_GREEN_SIZE, 0);
final int blueSize = findConfigAttrib(egl, display, config,
EGL10.EGL_BLUE_SIZE, 0);
final int alphaSize = findConfigAttrib(egl, display, config,
EGL10.EGL_ALPHA_SIZE, 0);
final int sampleBuffers = findConfigAttrib(egl, display, config,
EGL10.EGL_SAMPLE_BUFFERS, 0);
final int samples = findConfigAttrib(egl, display, config,
EGL10.EGL_SAMPLES, 0);
final int depth = findConfigAttrib(egl, display, config,
EGL10.EGL_DEPTH_SIZE, 0);
final int stencil = findConfigAttrib(egl, display, config,
EGL10.EGL_STENCIL_SIZE, 0);
final int score = redSize + greenSize + blueSize + alphaSize +
sampleBuffers + samples - depth - stencil;
if (score > maxScore) {
maxScore = score;
configIndex = i;
}
}
return configs[configIndex];
}
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
EGLConfig config, int attribute, int defaultValue) {
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
}
}
private class ContextFactory implements EGLContextFactory {
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
@Override
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
EGL10.EGL_NONE };
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
mEGLContextClientVersion != 0 ? attrib_list : null);
}
@Override
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
if (!egl.eglDestroyContext(display, context)) {
Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
throw new RuntimeException("eglDestroyContex failed: " + GLUtils.getEGLErrorString(egl.eglGetError()));
}
}
}
}