EGLManager.kt
/*
* Copyright 2021 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 androidx.graphics.opengl.egl
import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLContext
import android.opengl.EGLSurface
import android.opengl.GLES20
import androidx.opengl.EGLExt
import androidx.opengl.EGLExt.Companion.EGL_KHR_SURFACELESS_CONTEXT
/**
* Class responsible for configuration of EGL related resources. This includes
* initialization of the corresponding EGL Display as well as EGL Context, among
* other EGL related facilities.
*/
@Suppress("AcronymName")
class EGLManager(eglSpec: EGLSpec = EGLSpec.V14) {
private var mEglConfig: EGLConfig? = null
/**
* Offscreen pixel buffer surface
*/
private var mPBufferSurface: EGLSurface = EGL14.EGL_NO_SURFACE
private var mEglContext: EGLContext = EGL14.EGL_NO_CONTEXT
private var mWideColorGamutSupport = false
private var mEglVersion = EGLVersion.Unknown
private val mEglSpec = eglSpec
private var mEglExtensions: Set<String>? = null
private var mIsSingleBuffered: Boolean = false
private var mQueryResult: IntArray? = null
/**
* Initialize the EGLManager. This initializes the default display as well
* as queries the supported extensions
*/
fun initialize() {
mEglContext.let {
if (it === EGL14.EGL_NO_CONTEXT) {
mEglVersion = eglSpec.eglInitialize()
mEglExtensions =
EGLExt.parseExtensions(eglSpec.eglQueryString(EGL14.EGL_EXTENSIONS))
}
}
}
/**
* Attempt to load an [EGLConfig] instance from the given
* [EGLConfigAttributes]. If the [EGLConfig] could not be loaded
* this returns null
*/
fun loadConfig(configAttributes: EGLConfigAttributes): EGLConfig? =
eglSpec.loadConfig(configAttributes)
/**
* Creates an [EGLContext] from the given [EGLConfig] returning
* null if the context could not be created
*
* @throws EGLException if the default surface could not be made current after context creation
*/
fun createContext(config: EGLConfig): EGLContext {
val eglContext = eglSpec.eglCreateContext(config)
if (eglContext !== EGL14.EGL_NO_CONTEXT) {
val pbBufferSurface: EGLSurface =
if (isExtensionSupported(EGL_KHR_SURFACELESS_CONTEXT)) {
EGL14.EGL_NO_SURFACE
} else {
val configAttrs = EGLConfigAttributes {
EGL14.EGL_WIDTH to 1
EGL14.EGL_HEIGHT to 1
}
eglSpec.eglCreatePBufferSurface(config, configAttrs)
}
if (!eglSpec.eglMakeCurrent(eglContext, pbBufferSurface, pbBufferSurface)) {
throw EGLException(eglSpec.eglGetError(), "Unable to make default surface current")
}
mPBufferSurface = pbBufferSurface
mEglContext = eglContext
mEglConfig = config
} else {
mPBufferSurface = EGL14.EGL_NO_SURFACE
mEglContext = EGL14.EGL_NO_CONTEXT
mEglConfig = null
}
return eglContext
}
/**
* Release the resources allocated by EGLManager. This will destroy the corresponding
* EGLContext instance if it was previously initialized.
* The configured EGLVersion as well as EGLExtensions
*/
fun release() {
mEglContext.let {
if (it != EGL14.EGL_NO_CONTEXT) {
eglSpec.eglDestroyContext(it)
mPBufferSurface.let { pbBufferSurface ->
if (pbBufferSurface != EGL14.EGL_NO_SURFACE) {
eglSpec.eglDestroySurface(pbBufferSurface)
}
}
mPBufferSurface = EGL14.EGL_NO_SURFACE
eglSpec.eglMakeCurrent(
EGL14.EGL_NO_CONTEXT,
EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_SURFACE
)
mEglVersion = EGLVersion.Unknown
mEglContext = EGL14.EGL_NO_CONTEXT
mEglConfig = null
mEglExtensions = null
}
}
}
val eglSpec: EGLSpec
@Suppress("AcronymName")
@JvmName("getEGLSpec")
get() = mEglSpec
/**
* Returns the EGL version that is supported. This parameter is configured
* after [initialize] is invoked.
*/
val eglVersion: EGLVersion
@Suppress("AcronymName")
@JvmName("getEGLVersion")
get() = mEglVersion
/**
* Returns the current EGLContext. This parameter is configured after [initialize] is invoked
*/
val eglContext: EGLContext?
@Suppress("AcronymName")
@JvmName("getEGLContext")
get() = mEglContext
/**
* Returns the [EGLConfig] used to load the current [EGLContext].
* This is configured after [createContext] is invoked.
*/
val eglConfig: EGLConfig?
@Suppress("AcronymName")
@JvmName("getEGLConfig")
get() = mEglConfig
/**
* Determines whether the extension with the provided name is supported. The string
* provided is expected to be one of the named extensions defined within the OpenGL
* extension documentation.
*
* See [EGLExt] for additional documentation for given extension name constants
* and descriptions.
*
* The set of supported extensions is configured after [initialize] is invoked.
* Attempts to query support for any extension beforehand will return false.
*/
fun isExtensionSupported(extensionName: String): Boolean =
mEglExtensions?.contains(extensionName) ?: false
/**
* Binds the current context to the given draw and read surfaces.
* The draw surface is used for all operations except for any pixel data read back or
* copy operations which are taken from the read surface.
*
* The same EGLSurface may be specified for both draw and read surfaces.
*
* If the context is not previously configured, the only valid parameters for the
* draw and read surfaces is [EGL14.EGL_NO_SURFACE]. This is useful to make sure there is
* always a surface specified and to release the current context without assigning a new one.
*
* See https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml
*
* @param drawSurface Surface used for all operations that involve writing pixel information
* @param readSurface Surface used for pixel data read back or copy operations. By default this
* is the same as [drawSurface]
*/
@JvmOverloads
fun makeCurrent(drawSurface: EGLSurface, readSurface: EGLSurface = drawSurface): Boolean {
val result = eglSpec.eglMakeCurrent(mEglContext, drawSurface, readSurface)
if (result) {
querySurface(drawSurface)
}
return result
}
/**
* Post EGL surface color buffer to a native window. If the current drawing surface
* is single buffered this will flush the buffer
*/
fun swapAndFlushBuffers() {
if (mIsSingleBuffered) {
GLES20.glFlush()
}
eglSpec.eglSwapBuffers(currentDrawSurface)
}
/**
* Returns the default surface. This can be an offscreen pixel buffer surface or
* [EGL14.EGL_NO_SURFACE] if the surfaceless context extension is supported.
*/
val defaultSurface: EGLSurface
get() = mPBufferSurface
/**
* Returns the current surface used for drawing pixel content
*/
val currentDrawSurface: EGLSurface
get() = eglSpec.eglGetCurrentDrawSurface()
/**
* Returns the current surface used for reading back or copying pixels
*/
val currentReadSurface: EGLSurface
get() = eglSpec.eglGetCurrentReadSurface()
/**
* Helper method to query properties of the given surface
*/
private fun querySurface(surface: EGLSurface) {
if (surface == EGL14.EGL_NO_SURFACE) {
mIsSingleBuffered = false
} else {
val resultArray = mQueryResult ?: IntArray(1).also { mQueryResult = it }
if (eglSpec.eglQuerySurface(surface, EGL14.EGL_RENDER_BUFFER, resultArray, 0)) {
mIsSingleBuffered = resultArray[0] == EGL14.EGL_SINGLE_BUFFER
}
}
}
companion object {
private const val TAG = "EglManager"
}
}