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 constexpr char kExternalRgbaTextureFragmentShader[] = R"(#version 300 es
100 #extension GL_OES_EGL_image_external_essl3 : require
101 #extension GL_EXT_YUV_target : require
102 precision mediump float;
103 in vec2 vTextureCoord;
104 layout (yuv) out vec4 fragColor;
105 uniform samplerExternalOES uTexture;
106 void main() {
107 vec4 rgbaColor = texture(uTexture, vTextureCoord);
108 fragColor = vec4(rgb_2_yuv(rgbaColor.xyz, itu_601_full_range), 0.0);
109 })";
110
111 constexpr int kCoordsPerVertex = 3;
112
113 constexpr std::array<float, 12> kSquareCoords{
114 -1.f, -1.0f, 0.0f, // top left
115 -1.f, 1.f, 0.0f, // bottom left
116 1.0f, 1.f, 0.0f, // bottom right
117 1.0f, -1.0f, 0.0f}; // top right
118
119 constexpr std::array<float, 8> kTextureCoords{0.0f, 1.0f, // top left
120 0.0f, 0.0f, // bottom left
121 1.0f, 0.0f, // bottom right
122 1.0f, 1.0f}; // top right
123
124 constexpr std::array<uint8_t, 6> kDrawOrder{0, 1, 2, 0, 2, 3};
125
compileShader(GLenum shaderType,const char * src)126 GLuint compileShader(GLenum shaderType, const char* src) {
127 GLuint shader = glCreateShader(shaderType);
128 if (shader == 0) {
129 ALOGE("glCreateShader(shaderType=%x) error: %#x",
130 static_cast<unsigned int>(shaderType), glGetError());
131 return 0;
132 }
133
134 glShaderSource(shader, 1, &src, NULL);
135 glCompileShader(shader);
136
137 GLint compiled = 0;
138 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
139 if (!compiled) {
140 ALOGE("Compile of shader type %d failed", shaderType);
141 GLint infoLen = 0;
142 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
143 if (infoLen) {
144 char* buf = new char[infoLen];
145 if (buf) {
146 glGetShaderInfoLog(shader, infoLen, NULL, buf);
147 ALOGE("Compile log: %s", buf);
148 delete[] buf;
149 }
150 }
151 glDeleteShader(shader);
152 return 0;
153 }
154 return shader;
155 }
156
157 } // namespace
158
~EglProgram()159 EglProgram::~EglProgram() {
160 if (mProgram) {
161 glDeleteProgram(mProgram);
162 }
163 }
164
initialize(const char * vertexShaderSrc,const char * fragmentShaderSrc)165 bool EglProgram::initialize(const char* vertexShaderSrc,
166 const char* fragmentShaderSrc) {
167 GLuint vertexShaderId = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
168 if (checkEglError("compileShader(vertex)")) {
169 return false;
170 }
171 GLuint fragmentShaderId = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
172 if (checkEglError("compileShader(fragment)")) {
173 return false;
174 }
175
176 GLuint programId = glCreateProgram();
177
178 glAttachShader(programId, vertexShaderId);
179 glAttachShader(programId, fragmentShaderId);
180 glLinkProgram(programId);
181
182 GLint linkStatus = GL_FALSE;
183 glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
184 if (linkStatus != GL_TRUE) {
185 ALOGE("glLinkProgram failed");
186 GLint bufLength = 0;
187 glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength);
188 if (bufLength) {
189 char* buf = new char[bufLength];
190 if (buf) {
191 glGetProgramInfoLog(programId, bufLength, NULL, buf);
192 ALOGE("Link log: %s", buf);
193 delete[] buf;
194 }
195 }
196 glDeleteProgram(programId);
197 return false;
198 }
199
200 mProgram = programId;
201
202 mIsInitialized = true;
203 return mIsInitialized;
204 }
205
isInitialized() const206 bool EglProgram::isInitialized() const {
207 return mIsInitialized;
208 }
209
EglTestPatternProgram()210 EglTestPatternProgram::EglTestPatternProgram() {
211 if (initialize(kJuliaFractalVertexShader, kJuliaFractalFragmentShader)) {
212 ALOGV("Successfully initialized EGL shaders for test pattern program.");
213 } else {
214 ALOGE("Test pattern EGL shader program initialization failed.");
215 }
216
217 mCHandle = glGetUniformLocation(mProgram, "uC");
218 mPositionHandle = glGetAttribLocation(mProgram, "aPosition");
219 mTextureCoordHandle = glGetAttribLocation(mProgram, "aTextureCoord");
220
221 // Pass vertex array to draw.
222 glEnableVertexAttribArray(mPositionHandle);
223 // Prepare the triangle coordinate data.
224 glVertexAttribPointer(mPositionHandle, kCoordsPerVertex, GL_FLOAT, false,
225 kSquareCoords.size(), kSquareCoords.data());
226
227 glEnableVertexAttribArray(mTextureCoordHandle);
228 glVertexAttribPointer(mTextureCoordHandle, 2, GL_FLOAT, false,
229 kTextureCoords.size(), kTextureCoords.data());
230 }
231
~EglTestPatternProgram()232 EglTestPatternProgram::~EglTestPatternProgram() {
233 if (mPositionHandle != -1) {
234 glDisableVertexAttribArray(mPositionHandle);
235 }
236 if (mTextureCoordHandle != -1) {
237 glDisableVertexAttribArray(mTextureCoordHandle);
238 }
239 }
240
draw(const std::chrono::nanoseconds timestamp)241 bool EglTestPatternProgram::draw(const std::chrono::nanoseconds timestamp) {
242 // Load compiled shader.
243 glUseProgram(mProgram);
244 checkEglError("glUseProgram");
245
246 float time = float(timestamp.count() / 1e9) / 10;
247 const std::complex<float> c(std::sin(time) * 0.78f, std::cos(time) * 0.78f);
248
249 // Pass "C" constant value determining the Julia set to the shader.
250 glUniform2f(mCHandle, c.imag(), c.real());
251
252 // Draw triangle strip forming a square filling the viewport.
253 glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
254 kDrawOrder.data());
255 if (checkEglError("glDrawElements")) {
256 return false;
257 }
258
259 return true;
260 }
261
EglTextureProgram(const TextureFormat textureFormat)262 EglTextureProgram::EglTextureProgram(const TextureFormat textureFormat) {
263 if (!isGlExtensionSupported(kGlExtYuvTarget)) {
264 ALOGE(
265 "Cannot initialize external texture program due to missing "
266 "GL_EXT_YUV_target extension");
267 return;
268 }
269
270 const char* fragmentShaderSrc = textureFormat == TextureFormat::YUV
271 ? kExternalYuvTextureFragmentShader
272 : kExternalRgbaTextureFragmentShader;
273 if (initialize(kExternalTextureVertexShader, fragmentShaderSrc)) {
274 ALOGV("Successfully initialized EGL shaders for external texture program.");
275 } else {
276 ALOGE("External texture EGL shader program initialization failed.");
277 }
278
279 // Lookup and cache handles to uniforms & attributes.
280 mPositionHandle = glGetAttribLocation(mProgram, "aPosition");
281 mTextureCoordHandle = glGetAttribLocation(mProgram, "aTextureCoord");
282 mTransformMatrixHandle =
283 glGetUniformLocation(mProgram, "aTextureTransformMatrix");
284 mTextureHandle = glGetUniformLocation(mProgram, "uTexture");
285
286 // Pass vertex array to the shader.
287 glEnableVertexAttribArray(mPositionHandle);
288 glVertexAttribPointer(mPositionHandle, kCoordsPerVertex, GL_FLOAT, false,
289 kSquareCoords.size(), kSquareCoords.data());
290
291 // Pass texture coordinates corresponding to vertex array to the shader.
292 glEnableVertexAttribArray(mTextureCoordHandle);
293 glVertexAttribPointer(mTextureCoordHandle, 2, GL_FLOAT, false,
294 kTextureCoords.size(), kTextureCoords.data());
295 }
296
~EglTextureProgram()297 EglTextureProgram::~EglTextureProgram() {
298 if (mPositionHandle != -1) {
299 glDisableVertexAttribArray(mPositionHandle);
300 }
301 if (mTextureCoordHandle != -1) {
302 glDisableVertexAttribArray(mTextureCoordHandle);
303 }
304 }
305
draw(GLuint textureId,const std::array<float,16> & transformMatrix)306 bool EglTextureProgram::draw(GLuint textureId,
307 const std::array<float, 16>& transformMatrix) {
308 // Load compiled shader.
309 glUseProgram(mProgram);
310 if (checkEglError("glUseProgram")) {
311 return false;
312 }
313
314 // Pass transformation matrix for the texture coordinates.
315 glUniformMatrix4fv(mTransformMatrixHandle, 1, /*transpose=*/GL_FALSE,
316 transformMatrix.data());
317
318 // Configure texture for the shader.
319 glActiveTexture(GL_TEXTURE0);
320 glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
321 glUniform1i(mTextureHandle, 0);
322
323 // Draw triangle strip forming a square filling the viewport.
324 glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
325 kDrawOrder.data());
326 if (checkEglError("glDrawElements")) {
327 return false;
328 }
329
330 return true;
331 }
332
333 } // namespace virtualcamera
334 } // namespace companion
335 } // namespace android
336