[go: nahoru, domu]

blob: c45e203c271abed7600eedb3c6f3cf5b5104b49a [file] [log] [blame]
/*
* Copyright 2023 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
import android.graphics.SurfaceTexture
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.ShortBuffer
@RequiresApi(Build.VERSION_CODES.O)
internal class QuadTextureRenderer {
private var mSurfaceTexture: SurfaceTexture? = null
/**
* Array used to store 4 vertices of x and y coordinates
*/
private val mQuadCoords = FloatArray(8)
/**
* Transform to apply to the corresponding texture source
*/
private val mTextureTransform = FloatArray(16)
/**
* Handle to the quad position attribute
*/
private var mQuadPositionHandle = -1
/**
* Handle to the texture coordinate attribute
*/
private var mTexPositionHandle = -1
/**
* Handle to the texture sampler uniform
*/
private var mTextureUniformHandle: Int = -1
/**
* Handle to the MVP matrix uniform
*/
private var mViewProjectionMatrixHandle: Int = -1
/**
* Handle to texture transform matrix
*/
private var mTextureTransformHandle: Int = -1
/**
* GL Program used for rendering a quad with a texture
*/
private var mProgram: Int = -1
/**
* Handle to the vertex shader
*/
private var mVertexShader = -1
/**
* Handle to the fragment shader
*/
private var mFragmentShader = -1
/**
* Flag to indicate the resources associated with the shaders/texture has been
* released. If this is true all subsequent attempts to draw should be ignored
*/
private var mIsReleased = false
/**
* FloatBuffer used to specify quad coordinates
*/
private val mQuadrantCoordinatesBuffer: FloatBuffer =
ByteBuffer.allocateDirect(mQuadCoords.size * 4).run {
order(ByteOrder.nativeOrder())
asFloatBuffer().apply {
position(0)
}
}
init {
mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VertexShader)
mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FragmentShader)
mProgram = GLES20.glCreateProgram()
GLES20.glAttachShader(mProgram, mVertexShader)
GLES20.glAttachShader(mProgram, mFragmentShader)
GLES20.glLinkProgram(mProgram)
GLES20.glUseProgram(mProgram)
mQuadPositionHandle = GLES20.glGetAttribLocation(mProgram, aPosition)
mTexPositionHandle = GLES20.glGetAttribLocation(mProgram, aTexCoord)
mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, uTexture)
mViewProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, uVPMatrix)
mTextureTransformHandle = GLES20.glGetUniformLocation(mProgram, tVPMatrix)
// Enable blend
GLES20.glEnable(GLES20.GL_BLEND)
// Uses to prevent transparent area to turn in black
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
}
fun release() {
if (!mIsReleased) {
if (mVertexShader != -1) {
GLES20.glDeleteShader(mVertexShader)
mVertexShader = -1
}
if (mFragmentShader != -1) {
GLES20.glDeleteShader(mFragmentShader)
mFragmentShader = -1
}
if (mProgram != -1) {
GLES20.glDeleteProgram(mProgram)
mProgram = -1
}
mIsReleased = true
}
}
private fun configureQuad(width: Float, height: Float): FloatBuffer =
mQuadrantCoordinatesBuffer.apply {
put(mQuadCoords.apply {
this[0] = 0f // top left
this[1] = height
this[2] = 0f // bottom left
this[3] = 0f
this[4] = width // top right
this[5] = 0f
this[6] = width // bottom right
this[7] = height
})
position(0)
}
internal fun setSurfaceTexture(surfaceTexture: SurfaceTexture) {
mSurfaceTexture = surfaceTexture
}
fun draw(
mvpMatrix: FloatArray,
width: Float,
height: Float
) {
if (mIsReleased) {
Log.w(TAG, "Attempt to render when TextureRenderer has been released")
return
}
val textureSource = mSurfaceTexture
if (textureSource == null) {
Log.w(TAG, "Attempt to render without texture source")
return
}
GLES20.glUseProgram(mProgram)
textureSource.updateTexImage()
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR)
GLES20.glUniform1i(mTextureUniformHandle, 0)
GLES20.glUniformMatrix4fv(
mViewProjectionMatrixHandle,
1,
false,
mvpMatrix,
0)
GLES20.glUniformMatrix4fv(
mTextureTransformHandle,
1,
false,
mTextureTransform.apply {
textureSource.getTransformMatrix(this)
},
0
)
GLES20.glVertexAttribPointer(
mQuadPositionHandle,
CoordsPerVertex,
GLES20.GL_FLOAT,
false,
VertexStride,
configureQuad(width, height)
)
GLES20.glVertexAttribPointer(
mTexPositionHandle,
CoordsPerVertex,
GLES20.GL_FLOAT,
false,
VertexStride,
TextureCoordinatesBuffer
)
GLES20.glEnableVertexAttribArray(mQuadPositionHandle)
GLES20.glEnableVertexAttribArray(mTexPositionHandle)
GLES20.glDrawElements(
GLES20.GL_TRIANGLES,
DrawOrder.size,
GLES20.GL_UNSIGNED_SHORT,
DrawOrderBuffer
)
GLES20.glDisableVertexAttribArray(mQuadPositionHandle)
GLES20.glDisableVertexAttribArray(mTexPositionHandle)
}
companion object {
private val TAG = "TextureRenderer"
internal const val uVPMatrix = "uVPMatrix"
internal const val tVPMatrix = "tVPMatrix"
internal const val aPosition = "aPosition"
internal const val aTexCoord = "aTexCoord"
private const val vTexCoord = "vTexCoord"
internal const val uTexture = "uTexture"
internal const val VertexShader =
"""
uniform mat4 $uVPMatrix;
uniform mat4 $tVPMatrix;
attribute vec4 $aPosition;
attribute vec2 $aTexCoord;
varying vec2 $vTexCoord;
void main(void)
{
gl_Position = $uVPMatrix * $aPosition;
$vTexCoord = vec2($tVPMatrix * vec4($aTexCoord, 1.0, 1.0));
}
"""
internal const val FragmentShader =
"""
#extension GL_OES_EGL_image_external : require
precision highp float;
uniform samplerExternalOES $uTexture;
varying vec2 $vTexCoord;
void main(void){
gl_FragColor = texture2D($uTexture, $vTexCoord);
}
"""
internal const val CoordsPerVertex = 2
internal const val VertexStride = 4 * CoordsPerVertex
private val TextureCoordinates = floatArrayOf(
// x, y
0.0f, 1.0f, // top left
0.0f, 0.0f, // bottom left
1.0f, 0.0f, // bottom right
1.0f, 1.0f, // top right
)
/**
* FloatBuffer used to specify the texture coordinates
*/
private val TextureCoordinatesBuffer: FloatBuffer =
ByteBuffer.allocateDirect(TextureCoordinates.size * 4).run {
order(ByteOrder.nativeOrder())
asFloatBuffer().apply {
put(TextureCoordinates)
position(0)
}
}
private val DrawOrder = shortArrayOf(0, 1, 2, 0, 2, 3)
/**
* Convert short array to short buffer
*/
private val DrawOrderBuffer: ShortBuffer =
ByteBuffer.allocateDirect(DrawOrder.size * 2).run {
order(ByteOrder.nativeOrder())
asShortBuffer().apply {
put(DrawOrder)
position(0)
}
}
fun checkError(msg: String) {
val error = GLES20.glGetError()
if (error != GLES20.GL_NO_ERROR) {
Log.v(TAG, "GLError $msg: $error")
}
}
internal fun loadShader(type: Int, shaderCode: String): Int =
GLES20.glCreateShader(type).also { shader ->
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
}
}
}