blob: b86d21d68822ec1ff99bb0c7b7d92774b286d952 [file] [log] [blame]
/*
* 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 android.view;
import android.content.ComponentCallbacks2;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.opengl.GLUtils;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
import static javax.microedition.khronos.egl.EGL10.*;
/**
* Interface for rendering a ViewAncestor using hardware acceleration.
*
* @hide
*/
public abstract class HardwareRenderer {
static final String LOG_TAG = "HardwareRenderer";
/**
* Turn on to only refresh the parts of the screen that need updating.
* When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY}
* must also have the value "true".
*/
public static final boolean RENDER_DIRTY_REGIONS = true;
/**
* System property used to enable or disable dirty regions invalidation.
* This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true.
* The default value of this property is assumed to be true.
*
* Possible values:
* "true", to enable partial invalidates
* "false", to disable partial invalidates
*/
static final String RENDER_DIRTY_REGIONS_PROPERTY = "hwui.render_dirty_regions";
/**
* System property used to enable or disable vsync.
* The default value of this property is assumed to be false.
*
* Possible values:
* "true", to disable vsync
* "false", to enable vsync
*/
static final String DISABLE_VSYNC_PROPERTY = "hwui.disable_vsync";
/**
* System property used to debug EGL configuration choice.
*
* Possible values:
* "choice", print the chosen configuration only
* "all", print all possible configurations
*/
static final String PRINT_CONFIG_PROPERTY = "hwui.print_config";
/**
* Turn on to draw dirty regions every other frame.
*/
private static final boolean DEBUG_DIRTY_REGION = false;
/**
* A process can set this flag to false to prevent the use of hardware
* rendering.
*
* @hide
*/
public static boolean sRendererDisabled = false;
/**
* Further hardware renderer disabling for the system process.
*
* @hide
*/
public static boolean sSystemRendererDisabled = false;
private boolean mEnabled;
private boolean mRequested = true;
/**
* Invoke this method to disable hardware rendering in the current process.
*
* @hide
*/
public static void disable(boolean system) {
sRendererDisabled = true;
if (system) {
sSystemRendererDisabled = true;
}
}
/**
* Indicates whether hardware acceleration is available under any form for
* the view hierarchy.
*
* @return True if the view hierarchy can potentially be hardware accelerated,
* false otherwise
*/
public static boolean isAvailable() {
return GLES20Canvas.isAvailable();
}
/**
* Destroys the hardware rendering context.
*
* @param full If true, destroys all associated resources.
*/
abstract void destroy(boolean full);
/**
* Initializes the hardware renderer for the specified surface.
*
* @param holder The holder for the surface to hardware accelerate.
*
* @return True if the initialization was successful, false otherwise.
*/
abstract boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException;
/**
* Updates the hardware renderer for the specified surface.
*
* @param holder The holder for the surface to hardware accelerate
*/
abstract void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException;
/**
* Destoys the layers used by the specified view hierarchy.
*
* @param view The root of the view hierarchy
*/
abstract void destroyLayers(View view);
/**
* This method should be invoked whenever the current hardware renderer
* context should be reset.
*
* @param holder The holder for the surface to hardware accelerate
*/
abstract void invalidate(SurfaceHolder holder);
/**
* This method should be invoked to ensure the hardware renderer is in
* valid state (for instance, to ensure the correct EGL context is bound
* to the current thread.)
*
* @return true if the renderer is now valid, false otherwise
*/
abstract boolean validate();
/**
* Setup the hardware renderer for drawing. This is called whenever the
* size of the target surface changes or when the surface is first created.
*
* @param width Width of the drawing surface.
* @param height Height of the drawing surface.
*/
abstract void setup(int width, int height);
/**
* Gets the current width of the surface. This is the width that the surface
* was last set to in a call to {@link #setup(int, int)}.
*
* @return the current width of the surface
*/
abstract int getWidth();
/**
* Gets the current height of the surface. This is the height that the surface
* was last set to in a call to {@link #setup(int, int)}.
*
* @return the current width of the surface
*/
abstract int getHeight();
/**
* Interface used to receive callbacks whenever a view is drawn by
* a hardware renderer instance.
*/
interface HardwareDrawCallbacks {
/**
* Invoked before a view is drawn by a hardware renderer.
*
* @param canvas The Canvas used to render the view.
*/
void onHardwarePreDraw(HardwareCanvas canvas);
/**
* Invoked after a view is drawn by a hardware renderer.
*
* @param canvas The Canvas used to render the view.
*/
void onHardwarePostDraw(HardwareCanvas canvas);
}
/**
* Draws the specified view.
*
* @param view The view to draw.
* @param attachInfo AttachInfo tied to the specified view.
* @param callbacks Callbacks invoked when drawing happens.
* @param dirty The dirty rectangle to update, can be null.
*
* @return true if the dirty rect was ignored, false otherwise
*/
abstract boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty);
/**
* Creates a new display list that can be used to record batches of
* drawing operations.
*
* @return A new display list.
*/
abstract DisplayList createDisplayList();
/**
* Creates a new hardware layer. A hardware layer built by calling this
* method will be treated as a texture layer, instead of as a render target.
*
* @param isOpaque Whether the layer should be opaque or not
*
* @return A hardware layer
*/
abstract HardwareLayer createHardwareLayer(boolean isOpaque);
/**
* Creates a new hardware layer.
*
* @param width The minimum width of the layer
* @param height The minimum height of the layer
* @param isOpaque Whether the layer should be opaque or not
*
* @return A hardware layer
*/
abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
/**
* Creates a new {@link SurfaceTexture} that can be used to render into the
* specified hardware layer.
*
*
* @param layer The layer to render into using a {@link android.graphics.SurfaceTexture}
*
* @return A {@link SurfaceTexture}
*/
abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer);
/**
* Initializes the hardware renderer for the specified surface and setup the
* renderer for drawing, if needed. This is invoked when the ViewAncestor has
* potentially lost the hardware renderer. The hardware renderer should be
* reinitialized and setup when the render {@link #isRequested()} and
* {@link #isEnabled()}.
*
* @param width The width of the drawing surface.
* @param height The height of the drawing surface.
* @param attachInfo The
* @param holder
*/
void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
SurfaceHolder holder) throws Surface.OutOfResourcesException {
if (isRequested()) {
// We lost the gl context, so recreate it.
if (!isEnabled()) {
if (initialize(holder)) {
setup(width, height);
}
}
}
}
/**
* Creates a hardware renderer using OpenGL.
*
* @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
* @param translucent True if the surface is translucent, false otherwise
*
* @return A hardware renderer backed by OpenGL.
*/
static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
switch (glVersion) {
case 2:
return Gl20Renderer.create(translucent);
}
throw new IllegalArgumentException("Unknown GL version: " + glVersion);
}
/**
* Invoke this method when the system is running out of memory. This
* method will attempt to recover as much memory as possible, based on
* the specified hint.
*
* @param level Hint about the amount of memory that should be trimmed,
* see {@link android.content.ComponentCallbacks}
*/
static void trimMemory(int level) {
Gl20Renderer.trimMemory(level);
}
/**
* Indicates whether hardware acceleration is currently enabled.
*
* @return True if hardware acceleration is in use, false otherwise.
*/
boolean isEnabled() {
return mEnabled;
}
/**
* Indicates whether hardware acceleration is currently enabled.
*
* @param enabled True if the hardware renderer is in use, false otherwise.
*/
void setEnabled(boolean enabled) {
mEnabled = enabled;
}
/**
* Indicates whether hardware acceleration is currently request but not
* necessarily enabled yet.
*
* @return True if requested, false otherwise.
*/
boolean isRequested() {
return mRequested;
}
/**
* Indicates whether hardware acceleration is currently requested but not
* necessarily enabled yet.
*
* @return True to request hardware acceleration, false otherwise.
*/
void setRequested(boolean requested) {
mRequested = requested;
}
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
// These values are not exposed in our EGL APIs
static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
static final int EGL_OPENGL_ES2_BIT = 4;
static final int EGL_SURFACE_TYPE = 0x3033;
static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
static final int SURFACE_STATE_ERROR = 0;
static final int SURFACE_STATE_SUCCESS = 1;
static final int SURFACE_STATE_UPDATED = 2;
static EGL10 sEgl;
static EGLDisplay sEglDisplay;
static EGLConfig sEglConfig;
static final Object[] sEglLock = new Object[0];
int mWidth = -1, mHeight = -1;
static final ThreadLocal<EGLContext> sEglContextStorage = new ThreadLocal<EGLContext>();
EGLContext mEglContext;
Thread mEglThread;
EGLSurface mEglSurface;
GL mGl;
HardwareCanvas mCanvas;
int mFrameCount;
Paint mDebugPaint;
static boolean sDirtyRegions;
static final boolean sDirtyRegionsRequested;
static {
String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
//noinspection PointlessBooleanExpression,ConstantConditions
sDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
sDirtyRegionsRequested = sDirtyRegions;
}
boolean mDirtyRegionsEnabled;
final boolean mVsyncDisabled;
final int mGlVersion;
final boolean mTranslucent;
private boolean mDestroyed;
private final Rect mRedrawClip = new Rect();
GlRenderer(int glVersion, boolean translucent) {
mGlVersion = glVersion;
mTranslucent = translucent;
final String vsyncProperty = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false");
mVsyncDisabled = "true".equalsIgnoreCase(vsyncProperty);
if (mVsyncDisabled) {
Log.d(LOG_TAG, "Disabling v-sync");
}
}
/**
* Indicates whether this renderer instance can track and update dirty regions.
*/
boolean hasDirtyRegions() {
return mDirtyRegionsEnabled;
}
/**
* Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
* is invoked and the requested flag is turned off. The error code is
* also logged as a warning.
*/
void checkEglErrors() {
if (isEnabled()) {
int error = sEgl.eglGetError();
if (error != EGL_SUCCESS) {
// something bad has happened revert to
// normal rendering.
Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
fallback(error != EGL11.EGL_CONTEXT_LOST);
}
}
}
private void fallback(boolean fallback) {
destroy(true);
if (fallback) {
// we'll try again if it was context lost
setRequested(false);
Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
+ "Switching back to software rendering.");
}
}
@Override
boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException {
if (isRequested() && !isEnabled()) {
initializeEgl();
mGl = createEglSurface(holder);
mDestroyed = false;
if (mGl != null) {
int err = sEgl.eglGetError();
if (err != EGL_SUCCESS) {
destroy(true);
setRequested(false);
} else {
if (mCanvas == null) {
mCanvas = createCanvas();
}
if (mCanvas != null) {
setEnabled(true);
} else {
Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
}
}
return mCanvas != null;
}
}
return false;
}
@Override
void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
if (isRequested() && isEnabled()) {
createEglSurface(holder);
}
}
abstract GLES20Canvas createCanvas();
abstract int[] getConfig(boolean dirtyRegions);
void initializeEgl() {
synchronized (sEglLock) {
if (sEgl == null && sEglConfig == null) {
sEgl = (EGL10) EGLContext.getEGL();
// Get to the default display.
sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (sEglDisplay == EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed "
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
}
// We can now initialize EGL for that display
int[] version = new int[2];
if (!sEgl.eglInitialize(sEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed " +
GLUtils.getEGLErrorString(sEgl.eglGetError()));
}
sEglConfig = chooseEglConfig();
if (sEglConfig == null) {
// We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
if (sDirtyRegions) {
sDirtyRegions = false;
sEglConfig = chooseEglConfig();
if (sEglConfig == null) {
throw new RuntimeException("eglConfig not initialized");
}
} else {
throw new RuntimeException("eglConfig not initialized");
}
}
}
}
mEglContext = sEglContextStorage.get();
mEglThread = Thread.currentThread();
if (mEglContext == null) {
mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
sEglContextStorage.set(mEglContext);
}
}
private EGLConfig chooseEglConfig() {
EGLConfig[] configs = new EGLConfig[1];
int[] configsCount = new int[1];
int[] configSpec = getConfig(sDirtyRegions);
// Debug
final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
if ("all".equalsIgnoreCase(debug)) {
sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
configsCount[0], configsCount);
for (EGLConfig config : debugConfigs) {
printConfig(config);
}
}
if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
throw new IllegalArgumentException("eglChooseConfig failed " +
GLUtils.getEGLErrorString(sEgl.eglGetError()));
} else if (configsCount[0] > 0) {
if ("choice".equalsIgnoreCase(debug)) {
printConfig(configs[0]);
}
return configs[0];
}
return null;
}
private void printConfig(EGLConfig config) {
int[] value = new int[1];
Log.d(LOG_TAG, "EGL configuration " + config + ":");
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
Log.d(LOG_TAG, " RED_SIZE = " + value[0]);
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]);
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]);
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]);
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]);
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]);
sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
}
GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
// Check preconditions.
if (sEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (sEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (sEglConfig == null) {
throw new RuntimeException("eglConfig not initialized");
}
if (Thread.currentThread() != mEglThread) {
throw new IllegalStateException("HardwareRenderer cannot be used "
+ "from multiple threads");
}
// In case we need to destroy an existing surface
destroySurface();
// Create an EGL surface we can render into.
if (!createSurface(holder)) {
return null;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
}
// If mDirtyRegions is set, this means we have an EGL configuration
// with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
if (sDirtyRegions) {
if (!(mDirtyRegionsEnabled = GLES20Canvas.preserveBackBuffer())) {
Log.w(LOG_TAG, "Backbuffer cannot be preserved");
}
} else if (sDirtyRegionsRequested) {
// If mDirtyRegions is not set, our EGL configuration does not
// have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
// swap behavior might be EGL_BUFFER_PRESERVED, which means we
// want to set mDirtyRegions. We try to do this only if dirty
// regions were initially requested as part of the device
// configuration (see RENDER_DIRTY_REGIONS)
mDirtyRegionsEnabled = GLES20Canvas.isBackBufferPreserved();
}
return mEglContext.getGL();
}
EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
mGlVersion != 0 ? attribs : null);
}
@Override
void destroy(boolean full) {
if (full && mCanvas != null) {
mCanvas = null;
}
if (!isEnabled() || mDestroyed) {
setEnabled(false);
return;
}
destroySurface();
setEnabled(false);
mDestroyed = true;
mGl = null;
}
void destroySurface() {
if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
mEglSurface = null;
}
}
@Override
void invalidate(SurfaceHolder holder) {
// Cancels any existing buffer to ensure we'll get a buffer
// of the right size before we call eglSwapBuffers
sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
mEglSurface = null;
setEnabled(false);
}
if (holder.getSurface().isValid()) {
if (!createSurface(holder)) {
return;
}
if (mCanvas != null) {
setEnabled(true);
}
}
}
private boolean createSurface(SurfaceHolder holder) {
mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
int error = sEgl.eglGetError();
if (error == EGL_BAD_NATIVE_WINDOW) {
Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
return false;
}
throw new RuntimeException("createWindowSurface failed "
+ GLUtils.getEGLErrorString(error));
}
return true;
}
@Override
boolean validate() {
return checkCurrent() != SURFACE_STATE_ERROR;
}
@Override
void setup(int width, int height) {
if (validate()) {
mCanvas.setViewport(width, height);
mWidth = width;
mHeight = height;
}
}
@Override
int getWidth() {
return mWidth;
}
@Override
int getHeight() {
return mHeight;
}
boolean canDraw() {
return mGl != null && mCanvas != null;
}
void onPreDraw(Rect dirty) {
}
void onPostDraw() {
}
@Override
boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty) {
if (canDraw()) {
if (!hasDirtyRegions()) {
dirty = null;
}
attachInfo.mIgnoreDirtyState = true;
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
view.mPrivateFlags |= View.DRAWN;
final int surfaceState = checkCurrent();
if (surfaceState != SURFACE_STATE_ERROR) {
// We had to change the current surface and/or context, redraw everything
if (surfaceState == SURFACE_STATE_UPDATED) {
dirty = null;
}
onPreDraw(dirty);
HardwareCanvas canvas = mCanvas;
attachInfo.mHardwareCanvas = canvas;
int saveCount = canvas.save();
callbacks.onHardwarePreDraw(canvas);
try {
view.mRecreateDisplayList =
(view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED;
view.mPrivateFlags &= ~View.INVALIDATED;
DisplayList displayList = view.getDisplayList();
if (displayList != null) {
if (canvas.drawDisplayList(displayList, view.getWidth(),
view.getHeight(), mRedrawClip)) {
if (mRedrawClip.isEmpty() || view.getParent() == null) {
view.invalidate();
} else {
view.getParent().invalidateChild(view, mRedrawClip);
}
mRedrawClip.setEmpty();
}
} else {
// Shouldn't reach here
view.draw(canvas);
}
if (DEBUG_DIRTY_REGION) {
if (mDebugPaint == null) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0x7fff0000);
}
if (dirty != null && (mFrameCount++ & 1) == 0) {
canvas.drawRect(dirty, mDebugPaint);
}
}
} finally {
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
view.mRecreateDisplayList = false;
}
onPostDraw();
attachInfo.mIgnoreDirtyState = false;
sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
checkEglErrors();
return dirty == null;
}
}
return false;
}
/**
* Ensures the current EGL context is the one we expect.
*
* @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
* {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
* {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
*/
int checkCurrent() {
if (mEglThread != Thread.currentThread()) {
throw new IllegalStateException("Hardware acceleration can only be used with a " +
"single UI thread.\nOriginal thread: " + mEglThread + "\n" +
"Current thread: " + Thread.currentThread());
}
if (!mEglContext.equals(sEgl.eglGetCurrentContext()) ||
!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
Log.e(LOG_TAG, "eglMakeCurrent failed " +
GLUtils.getEGLErrorString(sEgl.eglGetError()));
fallback(true);
return SURFACE_STATE_ERROR;
} else {
return SURFACE_STATE_UPDATED;
}
}
return SURFACE_STATE_SUCCESS;
}
}
/**
* Hardware renderer using OpenGL ES 2.0.
*/
static class Gl20Renderer extends GlRenderer {
private GLES20Canvas mGlCanvas;
private static EGLSurface sPbuffer;
private static final Object[] sPbufferLock = new Object[0];
Gl20Renderer(boolean translucent) {
super(2, translucent);
}
@Override
GLES20Canvas createCanvas() {
return mGlCanvas = new GLES20Canvas(mTranslucent);
}
@Override
int[] getConfig(boolean dirtyRegions) {
return new int[] {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
(dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
EGL_NONE
};
}
@Override
boolean canDraw() {
return super.canDraw() && mGlCanvas != null;
}
@Override
void onPreDraw(Rect dirty) {
mGlCanvas.onPreDraw(dirty);
}
@Override
void onPostDraw() {
mGlCanvas.onPostDraw();
}
@Override
void destroy(boolean full) {
try {
super.destroy(full);
} finally {
if (full && mGlCanvas != null) {
mGlCanvas = null;
}
}
}
@Override
void setup(int width, int height) {
super.setup(width, height);
if (mVsyncDisabled) {
GLES20Canvas.disableVsync();
}
}
@Override
DisplayList createDisplayList() {
return new GLES20DisplayList();
}
@Override
HardwareLayer createHardwareLayer(boolean isOpaque) {
return new GLES20TextureLayer(isOpaque);
}
@Override
HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
return new GLES20RenderLayer(width, height, isOpaque);
}
@Override
SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
return ((GLES20TextureLayer) layer).getSurfaceTexture();
}
@Override
void destroyLayers(View view) {
if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) {
destroyHardwareLayer(view);
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
}
}
private void destroyHardwareLayer(View view) {
if (view.destroyLayer()) {
view.invalidate(true);
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
int count = group.getChildCount();
for (int i = 0; i < count; i++) {
destroyHardwareLayer(group.getChildAt(i));
}
}
}
static HardwareRenderer create(boolean translucent) {
if (GLES20Canvas.isAvailable()) {
return new Gl20Renderer(translucent);
}
return null;
}
static void trimMemory(int level) {
if (sEgl == null || sEglConfig == null) return;
EGLContext eglContext = sEglContextStorage.get();
// We do not have OpenGL objects
if (eglContext == null) {
return;
} else {
synchronized (sPbufferLock) {
// Create a temporary 1x1 pbuffer so we have a context
// to clear our OpenGL objects
if (sPbuffer == null) {
sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
});
}
}
sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
}
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
break;
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
break;
}
}
}
}