1 /*
2 * Copyright (C) 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 // #define LOG_NDEBUG 0
18 #define LOG_TAG "EglProgram"
19 #include "EglProgram.h"
20
21 #include <array>
22 #include <complex>
23
24 #include "EglUtil.h"
25 #include "GLES/gl.h"
26 #include "GLES2/gl2.h"
27 #include "GLES2/gl2ext.h"
28 #include "log/log.h"
29
30 namespace android {
31 namespace companion {
32 namespace virtualcamera {
33
34 namespace {
35
36 constexpr char kGlExtYuvTarget[] = "GL_EXT_YUV_target";
37
38 constexpr char kJuliaFractalVertexShader[] = R"(#version 300 es
39 in vec4 aPosition;
40 in vec2 aTextureCoord;
41 out vec2 vFractalCoord;
42 out vec2 vUVCoord;
43 void main() {
44 gl_Position = aPosition;
45 vUVCoord = aTextureCoord;
46 vFractalCoord = vec2(aTextureCoord.x - 0.5, aTextureCoord.y - 0.5) * 4.0;
47 })";
48
49 constexpr char kJuliaFractalFragmentShader[] = R"(#version 300 es
50 #extension GL_EXT_YUV_target : require
51 precision mediump float;
52
53 const float kIter = 64.0;
54
55 in vec2 vFractalCoord;
56 in vec2 vUVCoord;
57 out vec4 fragColor;
58 uniform vec2 uC;
59
60 vec2 imSq(vec2 n){
61 return vec2(pow(n.x,2.0)-pow(n.y,2.0), 2.0*n.x*n.y);
62 }
63
64 float julia(vec2 n, vec2 c) {
65 vec2 z = n;
66 for (float i=0.0;i<kIter; i+=1.0) {
67 z = imSq(z) + c;
68 if (length(z) > 2.0) return i/kIter;
69 }
70 return kIter;
71 }
72
73 void main() {
74 float juliaVal = julia(vFractalCoord, uC);
75 fragColor = vec4(yuv_2_rgb(vec3(juliaVal, vUVCoord.x, vUVCoord.y), itu_601_full_range), 0.0);
76 })";
77
78 constexpr char kExternalTextureVertexShader[] = R"(#version 300 es
79 uniform mat4 aTextureTransformMatrix; // Transform matrix given by surface texture.
80 in vec4 aPosition;
81 in vec2 aTextureCoord;
82 out vec2 vTextureCoord;
83 void main() {
84 gl_Position = aPosition;
85 vTextureCoord = (aTextureTransformMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
86 })";
87
88 constexpr char kExternalYuvTextureFragmentShader[] = R"(#version 300 es
89 #extension GL_OES_EGL_image_external_essl3 : require
90 #extension GL_EXT_YUV_target : require
91 precision mediump float;
92 in vec2 vTextureCoord;
93 layout (yuv) out vec4 fragColor;
94 uniform __samplerExternal2DY2YEXT uTexture;
95 void main() {
96 fragColor = texture(uTexture, vTextureCoord);
97 })";
98
99 // Shader to render a RGBA texture into a YUV buffer.
100 constexpr char kExternalRgbaTextureFragmentShader[] = R"(#version 300 es
101 #extension GL_OES_EGL_image_external_essl3 : require
102 #extension GL_EXT_YUV_target : require
103 precision mediump float;
104 in vec2 vTextureCoord;
105 layout (yuv) out vec4 fragColor;
106 uniform samplerExternalOES uTexture;
107 void main() {
108 vec4 rgbaColor = texture(uTexture, vTextureCoord);
109 fragColor = vec4(rgb_2_yuv(rgbaColor.xyz, itu_601_full_range), 0.0);
110 })";
111
112 constexpr int kCoordsPerVertex = 3;
113
114 constexpr std::array<float, 12> kSquareCoords{
115 -1.f, -1.0f, 0.0f, // top left
116 -1.f, 1.f, 0.0f, // bottom left
117 1.0f, 1.f, 0.0f, // bottom right
118 1.0f, -1.0f, 0.0f}; // top right
119
120 constexpr std::array<float, 8> kTextureCoords{0.0f, 1.0f, // top left
121 0.0f, 0.0f, // bottom left
122 1.0f, 0.0f, // bottom right
123 1.0f, 1.0f}; // top right
124
125 constexpr std::array<uint8_t, 6> kDrawOrder{0, 1, 2, 0, 2, 3};
126
compileShader(GLenum shaderType,const char * src)127 GLuint compileShader(GLenum shaderType, const char* src) {
128 GLuint shader = glCreateShader(shaderType);
129 if (shader == 0) {
130 ALOGE("glCreateShader(shaderType=%x) error: %#x",
131 static_cast<unsigned int>(shaderType), glGetError());
132 return 0;
133 }
134
135 glShaderSource(shader, 1, &src, NULL);
136 glCompileShader(shader);
137
138 GLint compiled = 0;
139 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
140 if (!compiled) {
141 ALOGE("Compile of shader type %d failed", shaderType);
142 GLint infoLen = 0;
143 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
144 if (infoLen) {
145 char* buf = new char[infoLen];
146 if (buf) {
147 glGetShaderInfoLog(shader, infoLen, NULL, buf);
148 ALOGE("Compile log: %s", buf);
149 delete[] buf;
150 }
151 }
152 glDeleteShader(shader);
153 return 0;
154 }
155 return shader;
156 }
157
158 } // namespace
159
~EglProgram()160 EglProgram::~EglProgram() {
161 if (mProgram) {
162 glDeleteProgram(mProgram);
163 }
164 }
165
initialize(const char * vertexShaderSrc,const char * fragmentShaderSrc)166 bool EglProgram::initialize(const char* vertexShaderSrc,
167 const char* fragmentShaderSrc) {
168 GLuint vertexShaderId = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
169 if (checkEglError("compileShader(vertex)")) {
170 return false;
171 }
172 GLuint fragmentShaderId = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
173 if (checkEglError("compileShader(fragment)")) {
174 return false;
175 }
176
177 GLuint programId = glCreateProgram();
178
179 glAttachShader(programId, vertexShaderId);
180 glAttachShader(programId, fragmentShaderId);
181 glLinkProgram(programId);
182
183 GLint linkStatus = GL_FALSE;
184 glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
185 if (linkStatus != GL_TRUE) {
186 ALOGE("glLinkProgram failed");
187 GLint bufLength = 0;
188 glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength);
189 if (bufLength) {
190 char* buf = new char[bufLength];
191 if (buf) {
192 glGetProgramInfoLog(programId, bufLength, NULL, buf);
193 ALOGE("Link log: %s", buf);
194 delete[] buf;
195 }
196 }
197 glDeleteProgram(programId);
198 return false;
199 }
200
201 mProgram = programId;
202
203 mIsInitialized = true;
204 return mIsInitialized;
205 }
206
isInitialized() const207 bool EglProgram::isInitialized() const {
208 return mIsInitialized;
209 }
210
EglTestPatternProgram()211 EglTestPatternProgram::EglTestPatternProgram() {
212 if (initialize(kJuliaFractalVertexShader, kJuliaFractalFragmentShader)) {
213 ALOGV("Successfully initialized EGL shaders for test pattern program.");
214 } else {
215 ALOGE("Test pattern EGL shader program initialization failed.");
216 }
217
218 mCHandle = glGetUniformLocation(mProgram, "uC");
219 mPositionHandle = glGetAttribLocation(mProgram, "aPosition");
220 mTextureCoordHandle = glGetAttribLocation(mProgram, "aTextureCoord");
221
222 // Pass vertex array to draw.
223 glEnableVertexAttribArray(mPositionHandle);
224 // Prepare the triangle coordinate data.
225 glVertexAttribPointer(mPositionHandle, kCoordsPerVertex, GL_FLOAT, false,
226 kSquareCoords.size(), kSquareCoords.data());
227
228 glEnableVertexAttribArray(mTextureCoordHandle);
229 glVertexAttribPointer(mTextureCoordHandle, 2, GL_FLOAT, false,
230 kTextureCoords.size(), kTextureCoords.data());
231 }
232
~EglTestPatternProgram()233 EglTestPatternProgram::~EglTestPatternProgram() {
234 if (mPositionHandle != -1) {
235 glDisableVertexAttribArray(mPositionHandle);
236 }
237 if (mTextureCoordHandle != -1) {
238 glDisableVertexAttribArray(mTextureCoordHandle);
239 }
240 }
241
draw(const std::chrono::nanoseconds timestamp)242 bool EglTestPatternProgram::draw(const std::chrono::nanoseconds timestamp) {
243 // Load compiled shader.
244 glUseProgram(mProgram);
245 checkEglError("glUseProgram");
246
247 float time = float(timestamp.count() / 1e9) / 10;
248 const std::complex<float> c(std::sin(time) * 0.78f, std::cos(time) * 0.78f);
249
250 // Pass "C" constant value determining the Julia set to the shader.
251 glUniform2f(mCHandle, c.imag(), c.real());
252
253 // Draw triangle strip forming a square filling the viewport.
254 glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
255 kDrawOrder.data());
256 if (checkEglError("glDrawElements")) {
257 return false;
258 }
259
260 return true;
261 }
262
EglTextureProgram(const TextureFormat textureFormat)263 EglTextureProgram::EglTextureProgram(const TextureFormat textureFormat) {
264 if (!isGlExtensionSupported(kGlExtYuvTarget)) {
265 ALOGE(
266 "Cannot initialize external texture program due to missing "
267 "GL_EXT_YUV_target extension");
268 return;
269 }
270
271 const char* fragmentShaderSrc = textureFormat == TextureFormat::YUV
272 ? kExternalYuvTextureFragmentShader
273 : kExternalRgbaTextureFragmentShader;
274 if (initialize(kExternalTextureVertexShader, fragmentShaderSrc)) {
275 ALOGV("Successfully initialized EGL shaders for external texture program.");
276 } else {
277 ALOGE("External texture EGL shader program initialization failed.");
278 }
279
280 // Lookup and cache handles to uniforms & attributes.
281 mPositionHandle = glGetAttribLocation(mProgram, "aPosition");
282 mTextureCoordHandle = glGetAttribLocation(mProgram, "aTextureCoord");
283 mTransformMatrixHandle =
284 glGetUniformLocation(mProgram, "aTextureTransformMatrix");
285 mTextureHandle = glGetUniformLocation(mProgram, "uTexture");
286
287 // Pass vertex array to the shader.
288 glEnableVertexAttribArray(mPositionHandle);
289 glVertexAttribPointer(mPositionHandle, kCoordsPerVertex, GL_FLOAT, false,
290 kSquareCoords.size(), kSquareCoords.data());
291
292 // Pass texture coordinates corresponding to vertex array to the shader.
293 glEnableVertexAttribArray(mTextureCoordHandle);
294 glVertexAttribPointer(mTextureCoordHandle, 2, GL_FLOAT, false,
295 kTextureCoords.size(), kTextureCoords.data());
296 }
297
~EglTextureProgram()298 EglTextureProgram::~EglTextureProgram() {
299 if (mPositionHandle != -1) {
300 glDisableVertexAttribArray(mPositionHandle);
301 }
302 if (mTextureCoordHandle != -1) {
303 glDisableVertexAttribArray(mTextureCoordHandle);
304 }
305 }
306
draw(GLuint textureId,const std::array<float,16> & transformMatrix)307 bool EglTextureProgram::draw(GLuint textureId,
308 const std::array<float, 16>& transformMatrix) {
309 // Load compiled shader.
310 glUseProgram(mProgram);
311 if (checkEglError("glUseProgram")) {
312 return false;
313 }
314
315 // Pass transformation matrix for the texture coordinates.
316 glUniformMatrix4fv(mTransformMatrixHandle, 1, /*transpose=*/GL_FALSE,
317 transformMatrix.data());
318
319 // Configure texture for the shader.
320 glActiveTexture(GL_TEXTURE0);
321 glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
322 glUniform1i(mTextureHandle, 0);
323
324 // Draw triangle strip forming a square filling the viewport.
325 glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
326 kDrawOrder.data());
327 if (checkEglError("glDrawElements")) {
328 return false;
329 }
330
331 return true;
332 }
333
334 } // namespace virtualcamera
335 } // namespace companion
336 } // namespace android
337