1 /* <lambda>null2 * Copyright 2023 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 18 19 import android.graphics.SurfaceTexture 20 import android.opengl.GLES11Ext 21 import android.opengl.GLES20 22 import android.os.Build 23 import android.util.Log 24 import androidx.annotation.RequiresApi 25 import java.nio.ByteBuffer 26 import java.nio.ByteOrder 27 import java.nio.FloatBuffer 28 import java.nio.ShortBuffer 29 30 @RequiresApi(Build.VERSION_CODES.O) 31 internal class QuadTextureRenderer { 32 33 private var mSurfaceTexture: SurfaceTexture? = null 34 35 /** Array used to store 4 vertices of x and y coordinates */ 36 private val mQuadCoords = FloatArray(8) 37 38 /** Transform to apply to the corresponding texture source */ 39 private val mTextureTransform = FloatArray(16) 40 41 /** Handle to the quad position attribute */ 42 private var mQuadPositionHandle = -1 43 44 /** Handle to the texture coordinate attribute */ 45 private var mTexPositionHandle = -1 46 47 /** Handle to the texture sampler uniform */ 48 private var mTextureUniformHandle: Int = -1 49 50 /** Handle to the MVP matrix uniform */ 51 private var mViewProjectionMatrixHandle: Int = -1 52 53 /** Handle to texture transform matrix */ 54 private var mTextureTransformHandle: Int = -1 55 56 /** GL Program used for rendering a quad with a texture */ 57 private var mProgram: Int = -1 58 59 /** Handle to the vertex shader */ 60 private var mVertexShader = -1 61 62 /** Handle to the fragment shader */ 63 private var mFragmentShader = -1 64 65 /** 66 * Flag to indicate the resources associated with the shaders/texture has been released. If this 67 * is true all subsequent attempts to draw should be ignored 68 */ 69 private var mIsReleased = false 70 71 /** FloatBuffer used to specify quad coordinates */ 72 private val mQuadrantCoordinatesBuffer: FloatBuffer = 73 ByteBuffer.allocateDirect(mQuadCoords.size * 4).run { 74 order(ByteOrder.nativeOrder()) 75 asFloatBuffer().apply { position(0) } 76 } 77 78 init { 79 mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VertexShader) 80 mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FragmentShader) 81 mProgram = GLES20.glCreateProgram() 82 83 GLES20.glAttachShader(mProgram, mVertexShader) 84 GLES20.glAttachShader(mProgram, mFragmentShader) 85 GLES20.glLinkProgram(mProgram) 86 GLES20.glUseProgram(mProgram) 87 88 mQuadPositionHandle = GLES20.glGetAttribLocation(mProgram, aPosition) 89 mTexPositionHandle = GLES20.glGetAttribLocation(mProgram, aTexCoord) 90 91 mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, uTexture) 92 mViewProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, uVPMatrix) 93 mTextureTransformHandle = GLES20.glGetUniformLocation(mProgram, tVPMatrix) 94 95 // Enable blend 96 GLES20.glEnable(GLES20.GL_BLEND) 97 // Uses to prevent transparent area to turn in black 98 GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA) 99 } 100 101 fun release() { 102 if (!mIsReleased) { 103 if (mVertexShader != -1) { 104 GLES20.glDeleteShader(mVertexShader) 105 mVertexShader = -1 106 } 107 108 if (mFragmentShader != -1) { 109 GLES20.glDeleteShader(mFragmentShader) 110 mFragmentShader = -1 111 } 112 113 if (mProgram != -1) { 114 GLES20.glDeleteProgram(mProgram) 115 mProgram = -1 116 } 117 118 mIsReleased = true 119 } 120 } 121 122 private fun configureQuad(width: Float, height: Float): FloatBuffer = 123 mQuadrantCoordinatesBuffer.apply { 124 put( 125 mQuadCoords.apply { 126 this[0] = 0f // top left 127 this[1] = height 128 this[2] = 0f // bottom left 129 this[3] = 0f 130 this[4] = width // top right 131 this[5] = 0f 132 this[6] = width // bottom right 133 this[7] = height 134 } 135 ) 136 position(0) 137 } 138 139 internal fun setSurfaceTexture(surfaceTexture: SurfaceTexture) { 140 mSurfaceTexture = surfaceTexture 141 } 142 143 fun draw(mvpMatrix: FloatArray, width: Float, height: Float) { 144 if (mIsReleased) { 145 Log.w(TAG, "Attempt to render when TextureRenderer has been released") 146 return 147 } 148 149 val textureSource = mSurfaceTexture 150 if (textureSource == null) { 151 Log.w(TAG, "Attempt to render without texture source") 152 return 153 } 154 155 GLES20.glUseProgram(mProgram) 156 textureSource.updateTexImage() 157 158 GLES20.glTexParameteri( 159 GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 160 GLES20.GL_TEXTURE_MIN_FILTER, 161 GLES20.GL_LINEAR 162 ) 163 GLES20.glTexParameteri( 164 GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 165 GLES20.GL_TEXTURE_MAG_FILTER, 166 GLES20.GL_LINEAR 167 ) 168 169 GLES20.glUniform1i(mTextureUniformHandle, 0) 170 171 GLES20.glUniformMatrix4fv(mViewProjectionMatrixHandle, 1, false, mvpMatrix, 0) 172 173 GLES20.glUniformMatrix4fv( 174 mTextureTransformHandle, 175 1, 176 false, 177 mTextureTransform.apply { textureSource.getTransformMatrix(this) }, 178 0 179 ) 180 181 GLES20.glVertexAttribPointer( 182 mQuadPositionHandle, 183 CoordsPerVertex, 184 GLES20.GL_FLOAT, 185 false, 186 VertexStride, 187 configureQuad(width, height) 188 ) 189 190 GLES20.glVertexAttribPointer( 191 mTexPositionHandle, 192 CoordsPerVertex, 193 GLES20.GL_FLOAT, 194 false, 195 VertexStride, 196 TextureCoordinatesBuffer 197 ) 198 199 GLES20.glEnableVertexAttribArray(mQuadPositionHandle) 200 GLES20.glEnableVertexAttribArray(mTexPositionHandle) 201 202 GLES20.glDrawElements( 203 GLES20.GL_TRIANGLES, 204 DrawOrder.size, 205 GLES20.GL_UNSIGNED_SHORT, 206 DrawOrderBuffer 207 ) 208 209 GLES20.glDisableVertexAttribArray(mQuadPositionHandle) 210 GLES20.glDisableVertexAttribArray(mTexPositionHandle) 211 } 212 213 companion object { 214 215 private val TAG = "TextureRenderer" 216 217 internal const val uVPMatrix = "uVPMatrix" 218 internal const val tVPMatrix = "tVPMatrix" 219 internal const val aPosition = "aPosition" 220 internal const val aTexCoord = "aTexCoord" 221 222 private const val vTexCoord = "vTexCoord" 223 internal const val uTexture = "uTexture" 224 225 internal const val VertexShader = 226 """ 227 uniform mat4 $uVPMatrix; 228 uniform mat4 $tVPMatrix; 229 attribute vec4 $aPosition; 230 attribute vec2 $aTexCoord; 231 varying vec2 $vTexCoord; 232 233 void main(void) 234 { 235 gl_Position = $uVPMatrix * $aPosition; 236 $vTexCoord = vec2($tVPMatrix * vec4($aTexCoord.x, 1.0 - $aTexCoord.y, 1.0, 1.0)); 237 } 238 """ 239 240 internal const val FragmentShader = 241 """ 242 #extension GL_OES_EGL_image_external : require 243 precision highp float; 244 245 uniform samplerExternalOES $uTexture; 246 247 varying vec2 $vTexCoord; 248 249 void main(void){ 250 gl_FragColor = texture2D($uTexture, $vTexCoord); 251 } 252 """ 253 254 internal const val CoordsPerVertex = 2 255 internal const val VertexStride = 4 * CoordsPerVertex 256 257 private val TextureCoordinates = 258 floatArrayOf( 259 // x, y 260 0.0f, 261 1.0f, // top left 262 0.0f, 263 0.0f, // bottom left 264 1.0f, 265 0.0f, // bottom right 266 1.0f, 267 1.0f, // top right 268 ) 269 270 /** FloatBuffer used to specify the texture coordinates */ 271 private val TextureCoordinatesBuffer: FloatBuffer = 272 ByteBuffer.allocateDirect(TextureCoordinates.size * 4).run { 273 order(ByteOrder.nativeOrder()) 274 asFloatBuffer().apply { 275 put(TextureCoordinates) 276 position(0) 277 } 278 } 279 280 private val DrawOrder = shortArrayOf(0, 1, 2, 0, 2, 3) 281 282 /** Convert short array to short buffer */ 283 private val DrawOrderBuffer: ShortBuffer = 284 ByteBuffer.allocateDirect(DrawOrder.size * 2).run { 285 order(ByteOrder.nativeOrder()) 286 asShortBuffer().apply { 287 put(DrawOrder) 288 position(0) 289 } 290 } 291 292 fun checkError(msg: String) { 293 val error = GLES20.glGetError() 294 if (error != GLES20.GL_NO_ERROR) { 295 Log.v(TAG, "GLError $msg: $error") 296 } 297 } 298 299 internal fun loadShader(type: Int, shaderCode: String): Int = 300 GLES20.glCreateShader(type).also { shader -> 301 GLES20.glShaderSource(shader, shaderCode) 302 GLES20.glCompileShader(shader) 303 } 304 } 305 } 306