1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.glwallpaper;
18 
19 import static android.opengl.EGL14.EGL_ALPHA_SIZE;
20 import static android.opengl.EGL14.EGL_BLUE_SIZE;
21 import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
22 import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
23 import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
24 import static android.opengl.EGL14.EGL_DEPTH_SIZE;
25 import static android.opengl.EGL14.EGL_EXTENSIONS;
26 import static android.opengl.EGL14.EGL_GREEN_SIZE;
27 import static android.opengl.EGL14.EGL_NONE;
28 import static android.opengl.EGL14.EGL_NO_CONTEXT;
29 import static android.opengl.EGL14.EGL_NO_DISPLAY;
30 import static android.opengl.EGL14.EGL_NO_SURFACE;
31 import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
32 import static android.opengl.EGL14.EGL_RED_SIZE;
33 import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
34 import static android.opengl.EGL14.EGL_STENCIL_SIZE;
35 import static android.opengl.EGL14.EGL_SUCCESS;
36 import static android.opengl.EGL14.eglChooseConfig;
37 import static android.opengl.EGL14.eglCreateContext;
38 import static android.opengl.EGL14.eglCreateWindowSurface;
39 import static android.opengl.EGL14.eglDestroyContext;
40 import static android.opengl.EGL14.eglDestroySurface;
41 import static android.opengl.EGL14.eglGetDisplay;
42 import static android.opengl.EGL14.eglGetError;
43 import static android.opengl.EGL14.eglInitialize;
44 import static android.opengl.EGL14.eglMakeCurrent;
45 import static android.opengl.EGL14.eglQueryString;
46 import static android.opengl.EGL14.eglSwapBuffers;
47 import static android.opengl.EGL14.eglTerminate;
48 
49 import android.opengl.EGLConfig;
50 import android.opengl.EGLContext;
51 import android.opengl.EGLDisplay;
52 import android.opengl.EGLSurface;
53 import android.opengl.GLUtils;
54 import android.text.TextUtils;
55 import android.util.Log;
56 import android.view.SurfaceHolder;
57 
58 import java.io.FileDescriptor;
59 import java.io.PrintWriter;
60 import java.util.Collections;
61 import java.util.HashSet;
62 import java.util.Set;
63 
64 /**
65  * A helper class to handle EGL management.
66  */
67 public class EglHelper {
68     private static final String TAG = EglHelper.class.getSimpleName();
69     private static final int OPENGLES_VERSION = 2;
70     // Below two constants make drawing at low priority, so other things can preempt our drawing.
71     private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
72     private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
73     private static final boolean DEBUG = true;
74 
75     private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
76     private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
77 
78     private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
79 
80     /**
81      * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
82      */
83     private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
84 
85     /**
86      * https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt
87      */
88     private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH =
89             "EGL_EXT_gl_colorspace_display_p3_passthrough";
90 
91     private EGLDisplay mEglDisplay;
92     private EGLConfig mEglConfig;
93     private EGLContext mEglContext;
94     private EGLSurface mEglSurface;
95     private final int[] mEglVersion = new int[2];
96     private boolean mEglReady;
97     private final Set<String> mExts;
98 
EglHelper()99     public EglHelper() {
100         mExts = new HashSet<>();
101         connectDisplay();
102     }
103 
104     /**
105      * Initialize render context.
106      * @param surfaceHolder surface holder.
107      * @param wideColorGamut claim if a wcg surface is necessary.
108      * @return true if the render context is ready.
109      */
init(SurfaceHolder surfaceHolder, boolean wideColorGamut)110     public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) {
111         if (!hasEglDisplay() && !connectDisplay()) {
112             Log.w(TAG, "Can not connect display, abort!");
113             return false;
114         }
115 
116         if (!eglInitialize(mEglDisplay, mEglVersion, 0 /* majorOffset */,
117                     mEglVersion, 1 /* minorOffset */)) {
118             Log.w(TAG, "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError()));
119             return false;
120         }
121 
122         mEglConfig = chooseEglConfig();
123         if (mEglConfig == null) {
124             Log.w(TAG, "eglConfig not initialized!");
125             return false;
126         }
127 
128         if (!createEglContext()) {
129             Log.w(TAG, "Can't create EGLContext!");
130             return false;
131         }
132 
133         if (!createEglSurface(surfaceHolder, wideColorGamut)) {
134             Log.w(TAG, "Can't create EGLSurface!");
135             return false;
136         }
137 
138         mEglReady = true;
139         return true;
140     }
141 
connectDisplay()142     private boolean connectDisplay() {
143         mExts.clear();
144         mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
145         if (!hasEglDisplay()) {
146             Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
147             return false;
148         }
149         String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
150         if (!TextUtils.isEmpty(queryString)) {
151             Collections.addAll(mExts, queryString.split(" "));
152         }
153         return true;
154     }
155 
checkExtensionCapability(String extName)156     boolean checkExtensionCapability(String extName) {
157         return mExts.contains(extName);
158     }
159 
getWcgCapability()160     int getWcgCapability() {
161         if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
162             return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
163         }
164         return 0;
165     }
166 
chooseEglConfig()167     private EGLConfig chooseEglConfig() {
168         int[] configsCount = new int[1];
169         EGLConfig[] configs = new EGLConfig[1];
170         int[] configSpec = getConfig();
171         if (!eglChooseConfig(mEglDisplay, configSpec, 0, configs, 0, 1, configsCount, 0)) {
172             Log.w(TAG, "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError()));
173             return null;
174         } else {
175             if (configsCount[0] <= 0) {
176                 Log.w(TAG, "eglChooseConfig failed, invalid configs count: " + configsCount[0]);
177                 return null;
178             } else {
179                 return configs[0];
180             }
181         }
182     }
183 
getConfig()184     private int[] getConfig() {
185         return new int[] {
186             EGL_RED_SIZE, 8,
187             EGL_GREEN_SIZE, 8,
188             EGL_BLUE_SIZE, 8,
189             EGL_ALPHA_SIZE, 0,
190             EGL_DEPTH_SIZE, 0,
191             EGL_STENCIL_SIZE, 0,
192             EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
193             EGL_CONFIG_CAVEAT, EGL_NONE,
194             EGL_NONE
195         };
196     }
197 
198     /**
199      * Prepare an EglSurface.
200      * @param surfaceHolder surface holder.
201      * @param wcg if need to support wcg.
202      * @return true if EglSurface is ready.
203      */
createEglSurface(SurfaceHolder surfaceHolder, boolean wcg)204     public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) {
205         if (DEBUG) {
206             Log.d(TAG, "createEglSurface start");
207         }
208 
209         if (hasEglDisplay() && surfaceHolder.getSurface().isValid()) {
210             int[] attrs = null;
211             int wcgCapability = getWcgCapability();
212             if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
213                 attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
214             }
215             mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */);
216         } else {
217             Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay()
218                     + ", has valid surface=" + surfaceHolder.getSurface().isValid());
219             return false;
220         }
221 
222         if (!hasEglSurface()) {
223             Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
224             return false;
225         }
226 
227         if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
228             Log.w(TAG, "eglMakeCurrent failed: " + GLUtils.getEGLErrorString(eglGetError()));
229             return false;
230         }
231 
232         if (DEBUG) {
233             Log.d(TAG, "createEglSurface done");
234         }
235         return true;
236     }
237 
askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset)238     EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) {
239         return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset);
240     }
241 
242     /**
243      * Destroy EglSurface.
244      */
destroyEglSurface()245     public void destroyEglSurface() {
246         if (hasEglSurface()) {
247             eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
248             eglDestroySurface(mEglDisplay, mEglSurface);
249             mEglSurface = EGL_NO_SURFACE;
250         }
251     }
252 
253     /**
254      * Check if we have a valid EglSurface.
255      * @return true if EglSurface is ready.
256      */
hasEglSurface()257     public boolean hasEglSurface() {
258         return mEglSurface != null && mEglSurface != EGL_NO_SURFACE;
259     }
260 
261     /**
262      * Prepare EglContext.
263      * @return true if EglContext is ready.
264      */
createEglContext()265     public boolean createEglContext() {
266         if (DEBUG) {
267             Log.d(TAG, "createEglContext start");
268         }
269 
270         int[] attrib_list = new int[5];
271         int idx = 0;
272         attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION;
273         attrib_list[idx++] = OPENGLES_VERSION;
274         if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) {
275             attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
276             attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG;
277         }
278         attrib_list[idx] = EGL_NONE;
279         if (hasEglDisplay()) {
280             mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
281         } else {
282             Log.w(TAG, "mEglDisplay is null");
283             return false;
284         }
285 
286         if (!hasEglContext()) {
287             Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
288             return false;
289         }
290 
291         if (DEBUG) {
292             Log.d(TAG, "createEglContext done");
293         }
294         return true;
295     }
296 
297     /**
298      * Destroy EglContext.
299      */
destroyEglContext()300     public void destroyEglContext() {
301         if (hasEglContext()) {
302             eglDestroyContext(mEglDisplay, mEglContext);
303             mEglContext = EGL_NO_CONTEXT;
304         }
305     }
306 
307     /**
308      * Check if we have EglContext.
309      * @return true if EglContext is ready.
310      */
hasEglContext()311     public boolean hasEglContext() {
312         return mEglContext != null && mEglContext != EGL_NO_CONTEXT;
313     }
314 
315     /**
316      * Check if we have EglDisplay.
317      * @return true if EglDisplay is ready.
318      */
hasEglDisplay()319     public boolean hasEglDisplay() {
320         return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY;
321     }
322 
323     /**
324      * Swap buffer to display.
325      * @return true if swap successfully.
326      */
swapBuffer()327     public boolean swapBuffer() {
328         boolean status = eglSwapBuffers(mEglDisplay, mEglSurface);
329         int error = eglGetError();
330         if (error != EGL_SUCCESS) {
331             Log.w(TAG, "eglSwapBuffers failed: " + GLUtils.getEGLErrorString(error));
332         }
333         return status;
334     }
335 
336     /**
337      * Destroy EglSurface and EglContext, then terminate EGL.
338      */
finish()339     public void finish() {
340         if (hasEglSurface()) {
341             destroyEglSurface();
342         }
343         if (hasEglContext()) {
344             destroyEglContext();
345         }
346         if (hasEglDisplay()) {
347             terminateEglDisplay();
348         }
349         mEglReady = false;
350     }
351 
terminateEglDisplay()352     void terminateEglDisplay() {
353         eglTerminate(mEglDisplay);
354         mEglDisplay = EGL_NO_DISPLAY;
355     }
356 
357     /**
358      * Called to dump current state.
359      * @param prefix prefix.
360      * @param fd fd.
361      * @param out out.
362      * @param args args.
363      */
dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args)364     public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
365         String eglVersion = mEglVersion[0] + "." + mEglVersion[1];
366         out.print(prefix); out.print("EGL version="); out.print(eglVersion);
367         out.print(", "); out.print("EGL ready="); out.print(mEglReady);
368         out.print(", "); out.print("has EglContext="); out.print(hasEglContext());
369         out.print(", "); out.print("has EglSurface="); out.println(hasEglSurface());
370 
371         int[] configs = getConfig();
372         StringBuilder sb = new StringBuilder();
373         sb.append('{');
374         for (int egl : configs) {
375             sb.append("0x").append(Integer.toHexString(egl)).append(",");
376         }
377         sb.setCharAt(sb.length() - 1, '}');
378         out.print(prefix); out.print("EglConfig="); out.println(sb.toString());
379     }
380 }
381