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