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