1 /*
2  * 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.hardware.HardwareBuffer
20 import android.opengl.EGL14
21 import android.opengl.EGLConfig
22 import android.opengl.EGLContext
23 import android.opengl.EGLSurface
24 import android.os.Build
25 import android.view.Surface
26 import androidx.annotation.RequiresApi
27 import androidx.opengl.EGLExt
28 import androidx.opengl.EGLExt.Companion.EGLClientWaitResult
29 import androidx.opengl.EGLExt.Companion.EGLSyncAttribute
30 import androidx.opengl.EGLExt.Companion.EGL_CONDITION_SATISFIED_KHR
31 import androidx.opengl.EGLExt.Companion.EGL_FALSE
32 import androidx.opengl.EGLExt.Companion.EGL_FOREVER_KHR
33 import androidx.opengl.EGLExt.Companion.EGL_SYNC_FLUSH_COMMANDS_BIT_KHR
34 import androidx.opengl.EGLExt.Companion.EGL_TIMEOUT_EXPIRED_KHR
35 import androidx.opengl.EGLExt.Companion.eglClientWaitSyncKHR
36 import androidx.opengl.EGLExt.Companion.eglDestroyImageKHR
37 import androidx.opengl.EGLExt.Companion.eglDestroySyncKHR
38 import androidx.opengl.EGLImageKHR
39 import androidx.opengl.EGLSyncKHR
40 
41 @JvmDefaultWithCompatibility
42 /**
43  * Interface for accessing various EGL facilities independent of EGL versions. That is each EGL
44  * version implements this specification.
45  *
46  * EGLSpec is not thread safe and is up to the caller of these methods to guarantee thread safety.
47  */
48 interface EGLSpec {
49 
50     /**
51      * Query for the capabilities associated with the given eglDisplay. The result contains a space
52      * separated list of the capabilities.
53      *
54      * @param nameId identifier for the EGL string to query
55      */
eglQueryStringnull56     fun eglQueryString(nameId: Int): String
57 
58     /**
59      * Create a Pixel Buffer surface with the corresponding [EGLConfigAttributes]. Accepted
60      * attributes are defined as part of the OpenGL specification here:
61      * https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreatePbufferSurface.xhtml
62      *
63      * If a pixel buffer surface could not be created, [EGL14.EGL_NO_SURFACE] is returned.
64      *
65      * @param config Specifies the EGL Frame buffer configuration that defines the frame buffer
66      *   resource available to the surface
67      * @param configAttributes Optional list of attributes for the pixel buffer surface
68      */
69     fun eglCreatePBufferSurface(
70         config: EGLConfig,
71         configAttributes: EGLConfigAttributes?
72     ): EGLSurface
73 
74     /**
75      * Creates an on screen EGL window surface from the given [Surface] and returns a handle to it.
76      *
77      * See https://khronos.org/registry/EGL/sdk/docs/man/html/eglCreateWindowSurface.xhtml
78      *
79      * @param config Specifies the EGL frame buffer configuration that defines the frame buffer
80      *   resource available to the surface
81      * @param surface Android surface to consume rendered content
82      * @param configAttributes Optional list of attributes for the specified surface
83      */
84     fun eglCreateWindowSurface(
85         config: EGLConfig,
86         surface: Surface,
87         configAttributes: EGLConfigAttributes?
88     ): EGLSurface
89 
90     /**
91      * Destroys an EGL surface.
92      *
93      * If the EGL surface is not current to any thread, eglDestroySurface destroys it immediately.
94      * Otherwise, surface is destroyed when it becomes not current to any thread. Furthermore,
95      * resources associated with a pbuffer surface are not released until all color buffers of that
96      * pbuffer bound to a texture object have been released. Deferral of surface destruction would
97      * still return true as deferral does not indicate a failure condition
98      *
99      * @return `true` if destruction of the EGLSurface was successful, false otherwise
100      */
101     fun eglDestroySurface(surface: EGLSurface): Boolean
102 
103     /**
104      * Binds the current context to the given draw and read surfaces. The draw surface is used for
105      * all operations except for any pixel data read back or copy operations which are taken from
106      * the read surface.
107      *
108      * The same EGLSurface may be specified for both draw and read surfaces.
109      *
110      * See https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml for more
111      * information
112      *
113      * @param context EGL rendering context to be attached to the surfaces
114      * @param drawSurface EGLSurface to draw pixels into.
115      * @param readSurface EGLSurface used for read/copy operations.
116      */
117     fun eglMakeCurrent(
118         context: EGLContext,
119         drawSurface: EGLSurface,
120         readSurface: EGLSurface
121     ): Boolean
122 
123     /**
124      * Return the current surface used for reading or copying pixels. If no context is current,
125      * [EGL14.EGL_NO_SURFACE] is returned
126      */
127     fun eglGetCurrentReadSurface(): EGLSurface
128 
129     /**
130      * Return the current surface used for drawing pixels. If no context is current,
131      * [EGL14.EGL_NO_SURFACE] is returned.
132      */
133     fun eglGetCurrentDrawSurface(): EGLSurface
134 
135     /**
136      * Initialize the EGL implementation and return the major and minor version of the EGL
137      * implementation through [EGLVersion]. If initialization fails, this returns
138      * [EGLVersion.Unknown]
139      */
140     fun eglInitialize(): EGLVersion
141 
142     /**
143      * Load a corresponding EGLConfig from the provided [EGLConfigAttributes] If the EGLConfig could
144      * not be loaded, null is returned
145      *
146      * @param configAttributes Desired [EGLConfigAttributes] to create an [EGLConfig]
147      * @return the [EGLConfig] with the provided [EGLConfigAttributes] or null if an [EGLConfig]
148      *   could not be created with the specified attributes
149      */
150     fun loadConfig(configAttributes: EGLConfigAttributes): EGLConfig?
151 
152     /**
153      * Create an EGLContext with the default display. If createContext fails to create a rendering
154      * context, EGL_NO_CONTEXT is returned
155      *
156      * @param config [EGLConfig] used to create the [EGLContext]
157      */
158     fun eglCreateContext(config: EGLConfig): EGLContext
159 
160     /**
161      * Destroy the given EGLContext generated in [eglCreateContext]
162      *
163      * See https://khronos.org/registry/EGL/sdk/docs/man/html/eglDestroyContext.xhtml
164      *
165      * @param eglContext EGL rendering context to be destroyed
166      */
167     fun eglDestroyContext(eglContext: EGLContext)
168 
169     /**
170      * Post EGL surface color buffer to a native window
171      *
172      * See https://khronos.org/registry/EGL/sdk/docs/man/html/eglSwapBuffers.xhtml
173      *
174      * @param surface Specifies the EGL drawing surface whose buffers are to be swapped
175      * @return `true` if swapping of buffers succeeds, false otherwise
176      */
177     fun eglSwapBuffers(surface: EGLSurface): Boolean
178 
179     /**
180      * Query the EGL attributes of the provided surface
181      *
182      * @param surface EGLSurface to be queried
183      * @param attribute EGL attribute to query on the given EGL Surface
184      * @param result Int array to store the result of the query
185      * @param offset Index within [result] to store the value of the queried attribute
186      * @return `true` if the query was completed successfully, false otherwise. If the query fails,
187      *   [result] is unmodified
188      */
189     fun eglQuerySurface(surface: EGLSurface, attribute: Int, result: IntArray, offset: Int): Boolean
190 
191     /**
192      * Returns the error of the last called EGL function in the current thread. Initially, the error
193      * is set to EGL_SUCCESS. When an EGL function could potentially generate several different
194      * errors (for example, when passed both a bad attribute name, and a bad attribute value for a
195      * legal attribute name), the implementation may choose to generate any one of the applicable
196      * errors.
197      *
198      * See https://khronos.org/registry/EGL/sdk/docs/man/html/eglGetError.xhtml for more information
199      * and error codes that could potentially be returned
200      */
201     fun eglGetError(): Int
202 
203     /**
204      * Convenience method to obtain the corresponding error string from the error code obtained from
205      * [EGLSpec.eglGetError]
206      */
207     fun getErrorMessage(): String = getStatusString(eglGetError())
208 
209     /**
210      * Creates an EGLImage from the provided [HardwareBuffer]. This handles internally creating an
211      * EGLClientBuffer and an [EGLImageKHR] from the client buffer.
212      *
213      * When this [EGLImageKHR] instance is no longer necessary, consumers should be sure to call the
214      * corresponding method [eglDestroyImageKHR] to deallocate the resource.
215      *
216      * @param hardwareBuffer Backing [HardwareBuffer] for the generated EGLImage instance
217      * @return an [EGLImageKHR] instance representing the [EGLImageKHR] created from the
218      *   HardwareBuffer. Because this is created internally through EGL's eglCreateImageKR method,
219      *   this has the KHR suffix.
220      *
221      * This can return null if the EGL_ANDROID_image_native_buffer and EGL_KHR_image_base extensions
222      * are not supported or if allocation of the buffer fails.
223      *
224      * See www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt
225      */
226     @RequiresApi(Build.VERSION_CODES.O)
227     fun eglCreateImageFromHardwareBuffer(hardwareBuffer: HardwareBuffer): EGLImageKHR?
228 
229     /**
230      * Destroy the given [EGLImageKHR] instance. Once destroyed, the image may not be used to create
231      * any additional [EGLImageKHR] target resources within any client API contexts, although
232      * existing [EGLImageKHR] siblings may continue to be used. `true` is returned if
233      * DestroyImageKHR succeeds, `false` indicates failure. This can return `false` if the
234      * corresponding [EGLContext] is not valid.
235      *
236      * See: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_image_base.txt
237      *
238      * @param image EGLImageKHR to be destroyed
239      * @return `true` if the destruction of the EGLImageKHR object was successful, `false` otherwise
240      */
241     fun eglDestroyImageKHR(image: EGLImageKHR): Boolean
242 
243     /**
244      * Creates a sync object of the specified type associated with the specified display, and
245      * returns a handle to the new object. The configuration of the returned [EGLSyncKHR] object is
246      * specified by the provided attributes.
247      *
248      * Consumers should ensure that the EGL_KHR_fence_sync EGL extension is supported before
249      * invoking this method otherwise a null EGLSyncFenceKHR object is returned.
250      *
251      * When the [EGLSyncKHR] instance is no longer necessary, consumers are encouraged to call
252      * [eglDestroySyncKHR] to deallocate this resource.
253      *
254      * See: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_fence_sync.txt
255      *
256      * @param type Indicates the type of sync object that is returned
257      * @param attributes Specifies the configuration of the sync object returned
258      * @return the [EGLSyncKHR] object to be used as a fence or null if this extension is not
259      *   supported
260      */
261     fun eglCreateSyncKHR(type: Int, attributes: EGLConfigAttributes?): EGLSyncKHR?
262 
263     /**
264      * Query attributes of the provided sync object. Accepted attributes to query depend on the type
265      * of sync object. If no errors are generated, this returns true and the value of the queried
266      * attribute is stored in the value array at the offset position. If this method returns false,
267      * the provided value array is unmodified.
268      *
269      * See: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_fence_sync.txt
270      *
271      * @param sync EGLSyncKHR object to query attributes
272      * @param attribute Corresponding EGLSyncKHR attribute to query on [sync]
273      * @param value Integer array used to store the result of the query
274      * @param offset Index within the value array to store the result of the attribute query
275      * @return `true` if the attribute was queried successfully, false otherwise. Failure cases
276      *   include attempting to call this method on an invalid sync object, or the display provided
277      *   not matching the display that was used to create this sync object. Additionally if the
278      *   queried attribute is not supported for the sync object, false is returned.
279      */
280     fun eglGetSyncAttribKHR(
281         sync: EGLSyncKHR,
282         @EGLSyncAttribute attribute: Int,
283         value: IntArray,
284         offset: Int
285     ): Boolean
286 
287     /**
288      * Destroys the given sync object associated with the specified display
289      *
290      * Consumers should ensure that the EGL_KHR_fence_sync EGL extension is supported before
291      * invoking this method otherwise a null EGLSyncFenceKHR object is returned. See:
292      * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_fence_sync.txt
293      *
294      * @param sync Fence object to be destroyed
295      * @return `true` if the [EGLSyncKHR] object was destroyed successfully `false` otherwise. This
296      *   can return `false` if the sync object is not a valid sync object for the provided display
297      *   or if the display provided in this method does not match the display used to create this
298      *   sync in [eglCreateSyncKHR].
299      */
300     fun eglDestroySyncKHR(sync: EGLSyncKHR): Boolean
301 
302     /**
303      * Blocks the calling thread until the specified sync object is signalled or until
304      * [timeoutNanos] nanoseconds have passed. More than one [eglClientWaitSyncKHR] may be
305      * outstanding on the same [sync] at any given time. When there are multiple threads blocked on
306      * the same [sync] and the [sync] object has signalled, all such threads are released, but the
307      * order in which they are released is not defined.
308      *
309      * If the value of [timeoutNanos] is zero, then [eglClientWaitSyncKHR] simply tests the current
310      * status of sync. If the value of [timeoutNanos] is the special value [EGL_FOREVER_KHR], then
311      * [eglClientWaitSyncKHR] does not time out. For all other values, [timeoutNanos] is adjusted to
312      * the closest value allowed by the implementation-dependent timeout accuracy, which may be
313      * substantially longer than one nanosecond.
314      *
315      * [eglClientWaitSyncKHR] returns one of three status values describing the reason for
316      * returning. A return value of [EGL_TIMEOUT_EXPIRED_KHR] indicates that the specified timeout
317      * period expired before [sync] was signalled, or if [timeoutNanos] is zero, indicates that
318      * [sync] is not signaled. A return value of [EGL_CONDITION_SATISFIED_KHR] indicates that [sync]
319      * was signaled before the timeout expired, which includes the case when [sync] was already
320      * signaled when [eglClientWaitSyncKHR] was called. If an error occurs then an error is
321      * generated and [EGL_FALSE] is returned.
322      *
323      * If the sync object being blocked upon will not be signaled in finite time (for example by an
324      * associated fence command issued previously, but not yet flushed to the graphics pipeline),
325      * then [eglClientWaitSyncKHR] may wait forever. To help prevent this behavior, if the
326      * [EGL_SYNC_FLUSH_COMMANDS_BIT_KHR] is set on the flags parameter and the [sync] is unsignaled
327      * when [eglClientWaitSyncKHR] is called, then the equivalent flush will be performed for the
328      * current EGL context before blocking on sync. If no context is current bound for the API, the
329      * [EGL_SYNC_FLUSH_COMMANDS_BIT_KHR] bit is ignored.
330      *
331      * @param sync EGLSyncKHR object to wait on
332      * @param flags Optional flags to provide to handle flushing of pending commands
333      * @param timeoutNanos Optional timeout value to wait before this method returns, measured in
334      *   nanoseconds. This value is always consumed as an unsigned long value so even negative
335      *   values will be converted to their unsigned equivalent.
336      * @return Result code indicating the status of the wait request. Either
337      *   [EGL_CONDITION_SATISFIED_KHR], if the sync did signal within the specified timeout,
338      *   [EGL_TIMEOUT_EXPIRED_KHR] if the sync did not signal within the specified timeout, or
339      *   [EGL_FALSE] if an error occurs.
340      */
341     fun eglClientWaitSyncKHR(
342         sync: EGLSyncKHR,
343         flags: Int,
344         timeoutNanos: Long
345     ): @EGLClientWaitResult Int
346 
347     companion object {
348 
349         @JvmField
350         val V14 =
351             object : EGLSpec {
352 
353                 // Tuples of attribute identifiers along with their corresponding values.
354                 // EGL_NONE is used as a termination value similar to a null terminated string
355                 private val contextAttributes =
356                     intArrayOf(
357                         EGL14.EGL_CONTEXT_CLIENT_VERSION,
358                         2, // GLES VERSION 2
359                         // HWUI provides the ability to configure a context priority as well but
360                         // that only
361                         // seems to be configured on SystemUIApplication. This might be useful for
362                         // front buffer rendering situations for performance.
363                         EGL14.EGL_NONE
364                     )
365 
366                 override fun eglInitialize(): EGLVersion {
367                     // eglInitialize is destructive so create 2 separate arrays to store the major
368                     // and
369                     // minor version
370                     val major = intArrayOf(1)
371                     val minor = intArrayOf(1)
372                     val initializeResult =
373                         EGL14.eglInitialize(getDefaultDisplay(), major, 0, minor, 0)
374                     if (initializeResult) {
375                         return EGLVersion(major[0], minor[0])
376                     } else {
377                         throw EGLException(
378                             EGL14.eglGetError(),
379                             "Unable to initialize default display"
380                         )
381                     }
382                 }
383 
384                 override fun eglGetCurrentReadSurface(): EGLSurface =
385                     EGL14.eglGetCurrentSurface(EGL14.EGL_READ)
386 
387                 override fun eglGetCurrentDrawSurface(): EGLSurface =
388                     EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)
389 
390                 override fun eglQueryString(nameId: Int): String =
391                     EGL14.eglQueryString(getDefaultDisplay(), nameId)
392 
393                 override fun eglCreatePBufferSurface(
394                     config: EGLConfig,
395                     configAttributes: EGLConfigAttributes?
396                 ): EGLSurface =
397                     EGL14.eglCreatePbufferSurface(
398                         getDefaultDisplay(),
399                         config,
400                         configAttributes?.attrs,
401                         0
402                     )
403 
404                 override fun eglCreateWindowSurface(
405                     config: EGLConfig,
406                     surface: Surface,
407                     configAttributes: EGLConfigAttributes?,
408                 ): EGLSurface =
409                     EGL14.eglCreateWindowSurface(
410                         getDefaultDisplay(),
411                         config,
412                         surface,
413                         configAttributes?.attrs ?: DefaultWindowSurfaceConfig.attrs,
414                         0
415                     )
416 
417                 override fun eglSwapBuffers(surface: EGLSurface): Boolean =
418                     EGL14.eglSwapBuffers(getDefaultDisplay(), surface)
419 
420                 override fun eglQuerySurface(
421                     surface: EGLSurface,
422                     attribute: Int,
423                     result: IntArray,
424                     offset: Int
425                 ): Boolean =
426                     EGL14.eglQuerySurface(getDefaultDisplay(), surface, attribute, result, offset)
427 
428                 override fun eglDestroySurface(surface: EGLSurface) =
429                     EGL14.eglDestroySurface(getDefaultDisplay(), surface)
430 
431                 override fun eglMakeCurrent(
432                     context: EGLContext,
433                     drawSurface: EGLSurface,
434                     readSurface: EGLSurface
435                 ): Boolean =
436                     EGL14.eglMakeCurrent(getDefaultDisplay(), drawSurface, readSurface, context)
437 
438                 override fun loadConfig(configAttributes: EGLConfigAttributes): EGLConfig? {
439                     val configs = arrayOfNulls<EGLConfig?>(1)
440                     return if (
441                         EGL14.eglChooseConfig(
442                             getDefaultDisplay(),
443                             configAttributes.attrs,
444                             0,
445                             configs,
446                             0,
447                             1,
448                             intArrayOf(1),
449                             0
450                         )
451                     ) {
452                         configs[0]
453                     } else {
454                         null
455                     }
456                 }
457 
458                 override fun eglCreateContext(config: EGLConfig): EGLContext {
459                     return EGL14.eglCreateContext(
460                         getDefaultDisplay(),
461                         config,
462                         EGL14.EGL_NO_CONTEXT, // not creating from a shared context
463                         contextAttributes,
464                         0
465                     )
466                 }
467 
468                 override fun eglDestroyContext(eglContext: EGLContext) {
469                     if (!EGL14.eglDestroyContext(getDefaultDisplay(), eglContext)) {
470                         throw EGLException(EGL14.eglGetError(), "Unable to destroy EGLContext")
471                     }
472                 }
473 
474                 @RequiresApi(Build.VERSION_CODES.Q)
475                 override fun eglCreateImageFromHardwareBuffer(
476                     hardwareBuffer: HardwareBuffer
477                 ): EGLImageKHR? =
478                     EGLExt.eglCreateImageFromHardwareBuffer(getDefaultDisplay(), hardwareBuffer)
479 
480                 override fun eglDestroyImageKHR(image: EGLImageKHR): Boolean =
481                     EGLExt.eglDestroyImageKHR(getDefaultDisplay(), image)
482 
483                 override fun eglCreateSyncKHR(
484                     type: Int,
485                     attributes: EGLConfigAttributes?
486                 ): EGLSyncKHR? = EGLExt.eglCreateSyncKHR(getDefaultDisplay(), type, attributes)
487 
488                 override fun eglGetSyncAttribKHR(
489                     sync: EGLSyncKHR,
490                     attribute: Int,
491                     value: IntArray,
492                     offset: Int
493                 ): Boolean =
494                     EGLExt.eglGetSyncAttribKHR(getDefaultDisplay(), sync, attribute, value, offset)
495 
496                 override fun eglDestroySyncKHR(sync: EGLSyncKHR): Boolean =
497                     EGLExt.eglDestroySyncKHR(getDefaultDisplay(), sync)
498 
499                 override fun eglGetError(): Int = EGL14.eglGetError()
500 
501                 override fun eglClientWaitSyncKHR(
502                     sync: EGLSyncKHR,
503                     flags: Int,
504                     timeoutNanos: Long
505                 ): Int = EGLExt.eglClientWaitSyncKHR(getDefaultDisplay(), sync, flags, timeoutNanos)
506 
507                 private fun getDefaultDisplay() = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
508 
509                 /**
510                  * EglConfigAttribute that provides the default attributes for an EGL window surface
511                  */
512                 private val DefaultWindowSurfaceConfig = EGLConfigAttributes {}
513             }
514 
515         /**
516          * Return a string representation of the corresponding EGL status code. If the provided
517          * error value is not an EGL status code, the hex representation is returned instead
518          */
519         @JvmStatic
520         fun getStatusString(error: Int): String =
521             when (error) {
522                 EGL14.EGL_SUCCESS -> "EGL_SUCCESS"
523                 EGL14.EGL_NOT_INITIALIZED -> "EGL_NOT_INITIALIZED"
524                 EGL14.EGL_BAD_ACCESS -> "EGL_BAD_ACCESS"
525                 EGL14.EGL_BAD_ALLOC -> "EGL_BAD_ALLOC"
526                 EGL14.EGL_BAD_ATTRIBUTE -> "EGL_BAD_ATTRIBUTE"
527                 EGL14.EGL_BAD_CONFIG -> "EGL_BAD_CONFIG"
528                 EGL14.EGL_BAD_CONTEXT -> "EGL_BAD_CONTEXT"
529                 EGL14.EGL_BAD_CURRENT_SURFACE -> "EGL_BAD_CURRENT_SURFACE"
530                 EGL14.EGL_BAD_DISPLAY -> "EGL_BAD_DISPLAY"
531                 EGL14.EGL_BAD_MATCH -> "EGL_BAD_MATCH"
532                 EGL14.EGL_BAD_NATIVE_PIXMAP -> "EGL_BAD_NATIVE_PIXMAP"
533                 EGL14.EGL_BAD_NATIVE_WINDOW -> "EGL_BAD_NATIVE_WINDOW"
534                 EGL14.EGL_BAD_PARAMETER -> "EGL_BAD_PARAMETER"
535                 EGL14.EGL_BAD_SURFACE -> "EGL_BAD_SURFACE"
536                 EGL14.EGL_CONTEXT_LOST -> "EGL_CONTEXT_LOST"
537                 else -> Integer.toHexString(error)
538             }
539     }
540 }
541 
542 /**
543  * Exception class for reporting errors with EGL
544  *
545  * @param error Error code reported via eglGetError
546  * @param msg Optional message describing the exception being thrown
547  */
548 class EGLException(val error: Int, val msg: String = "") : RuntimeException() {
549 
550     override val message: String
551         get() = "Error: ${EGLSpec.getStatusString(error)}, $msg"
552 }
553 
554 /**
555  * Identifier for the current EGL implementation
556  *
557  * @param major Major version of the EGL implementation
558  * @param minor Minor version of the EGL implementation
559  */
560 @Suppress("DataClassDefinition")
561 data class EGLVersion(val major: Int, val minor: Int) {
562 
toStringnull563     override fun toString(): String {
564         return "EGL version $major.$minor"
565     }
566 
567     companion object {
568         /** Constant that represents version 1.4 of the EGL spec */
569         @JvmField val V14 = EGLVersion(1, 4)
570 
571         /** Constant that represents version 1.5 of the EGL spec */
572         @JvmField val V15 = EGLVersion(1, 5)
573 
574         /** Sentinel EglVersion value returned in error situations */
575         @JvmField val Unknown = EGLVersion(-1, -1)
576     }
577 }
578