1 /*
<lambda>null2  * Copyright 2021 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 androidx.graphics.opengl.egl
18 
19 import android.opengl.EGL14
20 import android.opengl.EGLConfig
21 import android.opengl.EGLContext
22 import android.opengl.EGLSurface
23 import android.opengl.GLES20
24 import androidx.opengl.EGLExt
25 import androidx.opengl.EGLExt.Companion.EGL_KHR_SURFACELESS_CONTEXT
26 
27 /**
28  * Class responsible for configuration of EGL related resources. This includes initialization of the
29  * corresponding EGL Display as well as EGL Context, among other EGL related facilities.
30  */
31 class EGLManager(eglSpec: EGLSpec = EGLSpec.V14) {
32 
33     private var mEglConfig: EGLConfig? = null
34 
35     /** Offscreen pixel buffer surface */
36     private var mPBufferSurface: EGLSurface = EGL14.EGL_NO_SURFACE
37     private var mEglContext: EGLContext = EGL14.EGL_NO_CONTEXT
38     private var mEglVersion = EGLVersion.Unknown
39     private val mEglSpec = eglSpec
40     private var mEglExtensions: Set<String>? = null
41     private var mIsSingleBuffered: Boolean = false
42     private var mQueryResult: IntArray? = null
43 
44     /**
45      * Initialize the EGLManager. This initializes the default display as well as queries the
46      * supported extensions
47      */
48     fun initialize() {
49         mEglContext.let {
50             if (it === EGL14.EGL_NO_CONTEXT) {
51                 mEglVersion = eglSpec.eglInitialize()
52                 mEglExtensions =
53                     EGLExt.parseExtensions(eglSpec.eglQueryString(EGL14.EGL_EXTENSIONS))
54             }
55         }
56     }
57 
58     /**
59      * Attempt to load an [EGLConfig] instance from the given [EGLConfigAttributes]. If the
60      * [EGLConfig] could not be loaded this returns null
61      */
62     fun loadConfig(configAttributes: EGLConfigAttributes): EGLConfig? =
63         eglSpec.loadConfig(configAttributes)
64 
65     /**
66      * Creates an [EGLContext] from the given [EGLConfig] returning null if the context could not be
67      * created
68      *
69      * @throws EGLException if the default surface could not be made current after context creation
70      */
71     fun createContext(config: EGLConfig): EGLContext {
72         val eglContext = eglSpec.eglCreateContext(config)
73         if (eglContext !== EGL14.EGL_NO_CONTEXT) {
74             val pbBufferSurface: EGLSurface =
75                 if (isExtensionSupported(EGL_KHR_SURFACELESS_CONTEXT)) {
76                     EGL14.EGL_NO_SURFACE
77                 } else {
78                     val configAttrs = EGLConfigAttributes {
79                         EGL14.EGL_WIDTH to 1
80                         EGL14.EGL_HEIGHT to 1
81                     }
82                     eglSpec.eglCreatePBufferSurface(config, configAttrs)
83                 }
84             if (!eglSpec.eglMakeCurrent(eglContext, pbBufferSurface, pbBufferSurface)) {
85                 throw EGLException(eglSpec.eglGetError(), "Unable to make default surface current")
86             }
87             mPBufferSurface = pbBufferSurface
88             mEglContext = eglContext
89             mEglConfig = config
90         } else {
91             mPBufferSurface = EGL14.EGL_NO_SURFACE
92             mEglContext = EGL14.EGL_NO_CONTEXT
93             mEglConfig = null
94         }
95         return eglContext
96     }
97 
98     /**
99      * Release the resources allocated by EGLManager. This will destroy the corresponding EGLContext
100      * instance if it was previously initialized. The configured EGLVersion as well as EGLExtensions
101      */
102     fun release() {
103         mEglContext.let {
104             if (it != EGL14.EGL_NO_CONTEXT) {
105                 eglSpec.eglDestroyContext(it)
106                 mPBufferSurface.let { pbBufferSurface ->
107                     if (pbBufferSurface != EGL14.EGL_NO_SURFACE) {
108                         eglSpec.eglDestroySurface(pbBufferSurface)
109                     }
110                 }
111                 mPBufferSurface = EGL14.EGL_NO_SURFACE
112                 eglSpec.eglMakeCurrent(
113                     EGL14.EGL_NO_CONTEXT,
114                     EGL14.EGL_NO_SURFACE,
115                     EGL14.EGL_NO_SURFACE
116                 )
117                 mEglVersion = EGLVersion.Unknown
118                 mEglContext = EGL14.EGL_NO_CONTEXT
119                 mEglConfig = null
120                 mEglExtensions = null
121             }
122         }
123     }
124 
125     val eglSpec: EGLSpec
126         @JvmName("getEGLSpec") get() = mEglSpec
127 
128     /**
129      * Returns the EGL version that is supported. This parameter is configured after [initialize] is
130      * invoked.
131      */
132     val eglVersion: EGLVersion
133         @JvmName("getEGLVersion") get() = mEglVersion
134 
135     /**
136      * Returns the current EGLContext. This parameter is configured after [initialize] is invoked
137      */
138     val eglContext: EGLContext?
139         @JvmName("getEGLContext") get() = mEglContext
140 
141     /**
142      * Returns the [EGLConfig] used to load the current [EGLContext]. This is configured after
143      * [createContext] is invoked.
144      */
145     val eglConfig: EGLConfig?
146         @JvmName("getEGLConfig") get() = mEglConfig
147 
148     /**
149      * Determines whether the extension with the provided name is supported. The string provided is
150      * expected to be one of the named extensions defined within the OpenGL extension documentation.
151      *
152      * See [EGLExt] for additional documentation for given extension name constants and
153      * descriptions.
154      *
155      * The set of supported extensions is configured after [initialize] is invoked. Attempts to
156      * query support for any extension beforehand will return false.
157      */
158     fun isExtensionSupported(extensionName: String): Boolean =
159         mEglExtensions?.contains(extensionName) ?: false
160 
161     /**
162      * Binds the current context to the given draw and read surfaces. The draw surface is used for
163      * all operations except for any pixel data read back or copy operations which are taken from
164      * the read surface.
165      *
166      * The same EGLSurface may be specified for both draw and read surfaces.
167      *
168      * If the context is not previously configured, the only valid parameters for the draw and read
169      * surfaces is [EGL14.EGL_NO_SURFACE]. This is useful to make sure there is always a surface
170      * specified and to release the current context without assigning a new one.
171      *
172      * See https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml
173      *
174      * @param drawSurface Surface used for all operations that involve writing pixel information
175      * @param readSurface Surface used for pixel data read back or copy operations. By default this
176      *   is the same as [drawSurface]
177      */
178     @JvmOverloads
179     fun makeCurrent(drawSurface: EGLSurface, readSurface: EGLSurface = drawSurface): Boolean {
180         val result = eglSpec.eglMakeCurrent(mEglContext, drawSurface, readSurface)
181         if (result) {
182             querySurface(drawSurface)
183         }
184         return result
185     }
186 
187     /**
188      * Post EGL surface color buffer to a native window. If the current drawing surface is single
189      * buffered this will flush the buffer
190      */
191     fun swapAndFlushBuffers() {
192         if (mIsSingleBuffered) {
193             GLES20.glFlush()
194         }
195         eglSpec.eglSwapBuffers(currentDrawSurface)
196     }
197 
198     /**
199      * Returns the default surface. This can be an offscreen pixel buffer surface or
200      * [EGL14.EGL_NO_SURFACE] if the surfaceless context extension is supported.
201      */
202     val defaultSurface: EGLSurface
203         get() = mPBufferSurface
204 
205     /** Returns the current surface used for drawing pixel content */
206     val currentDrawSurface: EGLSurface
207         get() = eglSpec.eglGetCurrentDrawSurface()
208 
209     /** Returns the current surface used for reading back or copying pixels */
210     val currentReadSurface: EGLSurface
211         get() = eglSpec.eglGetCurrentReadSurface()
212 
213     /** Helper method to query properties of the given surface */
214     private fun querySurface(surface: EGLSurface) {
215         if (surface == EGL14.EGL_NO_SURFACE) {
216             mIsSingleBuffered = false
217         } else {
218             val resultArray = mQueryResult ?: IntArray(1).also { mQueryResult = it }
219             if (eglSpec.eglQuerySurface(surface, EGL14.EGL_RENDER_BUFFER, resultArray, 0)) {
220                 mIsSingleBuffered = resultArray[0] == EGL14.EGL_SINGLE_BUFFER
221             }
222         }
223     }
224 
225     companion object {
226         private const val TAG = "EglManager"
227     }
228 }
229