/* * Copyright (C) 2019 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.android.systemui.glwallpaper; import static android.opengl.EGL14.EGL_ALPHA_SIZE; import static android.opengl.EGL14.EGL_BLUE_SIZE; import static android.opengl.EGL14.EGL_CONFIG_CAVEAT; import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY; import static android.opengl.EGL14.EGL_DEPTH_SIZE; import static android.opengl.EGL14.EGL_EXTENSIONS; import static android.opengl.EGL14.EGL_GREEN_SIZE; import static android.opengl.EGL14.EGL_NONE; import static android.opengl.EGL14.EGL_NO_CONTEXT; import static android.opengl.EGL14.EGL_NO_DISPLAY; import static android.opengl.EGL14.EGL_NO_SURFACE; import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT; import static android.opengl.EGL14.EGL_RED_SIZE; import static android.opengl.EGL14.EGL_RENDERABLE_TYPE; import static android.opengl.EGL14.EGL_STENCIL_SIZE; import static android.opengl.EGL14.EGL_SUCCESS; import static android.opengl.EGL14.eglChooseConfig; import static android.opengl.EGL14.eglCreateContext; import static android.opengl.EGL14.eglCreateWindowSurface; import static android.opengl.EGL14.eglDestroyContext; import static android.opengl.EGL14.eglDestroySurface; import static android.opengl.EGL14.eglGetDisplay; import static android.opengl.EGL14.eglGetError; import static android.opengl.EGL14.eglInitialize; import static android.opengl.EGL14.eglMakeCurrent; import static android.opengl.EGL14.eglQueryString; import static android.opengl.EGL14.eglSwapBuffers; import static android.opengl.EGL14.eglTerminate; import android.opengl.EGLConfig; import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLSurface; import android.opengl.GLUtils; import android.text.TextUtils; import android.util.Log; import android.view.SurfaceHolder; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * A helper class to handle EGL management. */ public class EglHelper { private static final String TAG = EglHelper.class.getSimpleName(); private static final int OPENGLES_VERSION = 2; // Below two constants make drawing at low priority, so other things can preempt our drawing. private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100; private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103; private static final boolean DEBUG = true; private static final int EGL_GL_COLORSPACE_KHR = 0x309D; private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490; private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority"; /** * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt */ private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace"; /** * https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt */ private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH = "EGL_EXT_gl_colorspace_display_p3_passthrough"; private EGLDisplay mEglDisplay; private EGLConfig mEglConfig; private EGLContext mEglContext; private EGLSurface mEglSurface; private final int[] mEglVersion = new int[2]; private boolean mEglReady; private final Set mExts; public EglHelper() { mExts = new HashSet<>(); connectDisplay(); } /** * Initialize render context. * @param surfaceHolder surface holder. * @param wideColorGamut claim if a wcg surface is necessary. * @return true if the render context is ready. */ public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) { if (!hasEglDisplay() && !connectDisplay()) { Log.w(TAG, "Can not connect display, abort!"); return false; } if (!eglInitialize(mEglDisplay, mEglVersion, 0 /* majorOffset */, mEglVersion, 1 /* minorOffset */)) { Log.w(TAG, "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; } mEglConfig = chooseEglConfig(); if (mEglConfig == null) { Log.w(TAG, "eglConfig not initialized!"); return false; } if (!createEglContext()) { Log.w(TAG, "Can't create EGLContext!"); return false; } if (!createEglSurface(surfaceHolder, wideColorGamut)) { Log.w(TAG, "Can't create EGLSurface!"); return false; } mEglReady = true; return true; } private boolean connectDisplay() { mExts.clear(); mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (!hasEglDisplay()) { Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; } String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS); if (!TextUtils.isEmpty(queryString)) { Collections.addAll(mExts, queryString.split(" ")); } return true; } boolean checkExtensionCapability(String extName) { return mExts.contains(extName); } int getWcgCapability() { if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) { return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; } return 0; } private EGLConfig chooseEglConfig() { int[] configsCount = new int[1]; EGLConfig[] configs = new EGLConfig[1]; int[] configSpec = getConfig(); if (!eglChooseConfig(mEglDisplay, configSpec, 0, configs, 0, 1, configsCount, 0)) { Log.w(TAG, "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError())); return null; } else { if (configsCount[0] <= 0) { Log.w(TAG, "eglChooseConfig failed, invalid configs count: " + configsCount[0]); return null; } else { return configs[0]; } } } private int[] getConfig() { return new int[] { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE }; } /** * Prepare an EglSurface. * @param surfaceHolder surface holder. * @param wcg if need to support wcg. * @return true if EglSurface is ready. */ public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) { if (DEBUG) { Log.d(TAG, "createEglSurface start"); } if (hasEglDisplay() && surfaceHolder.getSurface().isValid()) { int[] attrs = null; int wcgCapability = getWcgCapability(); if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) { attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE}; } mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */); } else { Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay() + ", has valid surface=" + surfaceHolder.getSurface().isValid()); return false; } if (!hasEglSurface()) { Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; } if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { Log.w(TAG, "eglMakeCurrent failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; } if (DEBUG) { Log.d(TAG, "createEglSurface done"); } return true; } EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) { return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset); } /** * Destroy EglSurface. */ public void destroyEglSurface() { if (hasEglSurface()) { eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mEglDisplay, mEglSurface); mEglSurface = EGL_NO_SURFACE; } } /** * Check if we have a valid EglSurface. * @return true if EglSurface is ready. */ public boolean hasEglSurface() { return mEglSurface != null && mEglSurface != EGL_NO_SURFACE; } /** * Prepare EglContext. * @return true if EglContext is ready. */ public boolean createEglContext() { if (DEBUG) { Log.d(TAG, "createEglContext start"); } int[] attrib_list = new int[5]; int idx = 0; attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION; attrib_list[idx++] = OPENGLES_VERSION; if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) { attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG; } attrib_list[idx] = EGL_NONE; if (hasEglDisplay()) { mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0); } else { Log.w(TAG, "mEglDisplay is null"); return false; } if (!hasEglContext()) { Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError())); return false; } if (DEBUG) { Log.d(TAG, "createEglContext done"); } return true; } /** * Destroy EglContext. */ public void destroyEglContext() { if (hasEglContext()) { eglDestroyContext(mEglDisplay, mEglContext); mEglContext = EGL_NO_CONTEXT; } } /** * Check if we have EglContext. * @return true if EglContext is ready. */ public boolean hasEglContext() { return mEglContext != null && mEglContext != EGL_NO_CONTEXT; } /** * Check if we have EglDisplay. * @return true if EglDisplay is ready. */ public boolean hasEglDisplay() { return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY; } /** * Swap buffer to display. * @return true if swap successfully. */ public boolean swapBuffer() { boolean status = eglSwapBuffers(mEglDisplay, mEglSurface); int error = eglGetError(); if (error != EGL_SUCCESS) { Log.w(TAG, "eglSwapBuffers failed: " + GLUtils.getEGLErrorString(error)); } return status; } /** * Destroy EglSurface and EglContext, then terminate EGL. */ public void finish() { if (hasEglSurface()) { destroyEglSurface(); } if (hasEglContext()) { destroyEglContext(); } if (hasEglDisplay()) { terminateEglDisplay(); } mEglReady = false; } void terminateEglDisplay() { eglTerminate(mEglDisplay); mEglDisplay = EGL_NO_DISPLAY; } /** * Called to dump current state. * @param prefix prefix. * @param fd fd. * @param out out. * @param args args. */ public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { String eglVersion = mEglVersion[0] + "." + mEglVersion[1]; out.print(prefix); out.print("EGL version="); out.print(eglVersion); out.print(", "); out.print("EGL ready="); out.print(mEglReady); out.print(", "); out.print("has EglContext="); out.print(hasEglContext()); out.print(", "); out.print("has EglSurface="); out.println(hasEglSurface()); int[] configs = getConfig(); StringBuilder sb = new StringBuilder(); sb.append('{'); for (int egl : configs) { sb.append("0x").append(Integer.toHexString(egl)).append(","); } sb.setCharAt(sb.length() - 1, '}'); out.print(prefix); out.print("EglConfig="); out.println(sb.toString()); } }