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 #include <vector>
18 #include <math.h>
19 
20 #define GL_GLEXT_PROTOTYPES
21 #define EGL_EGLEXT_PROTOTYPES
22 #include <EGL/egl.h>
23 #include <EGL/eglext.h>
24 #include <GLES2/gl2.h>
25 #include <GLES2/gl2ext.h>
26 #undef EGL_EGLEXT_PROTOTYPES
27 #undef GL_GLEXT_PROTOTYPES
28 
29 #include "abc3d.h"
30 #include "debug.h"
31 
32 namespace android {
33 namespace hardware {
34 namespace camera {
35 namespace provider {
36 namespace implementation {
37 namespace abc3d {
38 namespace {
39 constexpr char kTag[] = "abc3d";
40 
dot3(const float a3[],const float b3[])41 float dot3(const float a3[], const float b3[]) {
42     return a3[0] * b3[0] + a3[1] * b3[1] + a3[2] * b3[2];
43 }
44 
45 /*
46  * https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
47  * https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml
48  * This function takes `m44` (where zzz (assumed zero) and ooo (assumed one)
49  * are ignored) and multiplies it by a translation matrix.
50  *
51  *           m44             translate
52  *  [  s0  s1  s2 zzz ]   [ 1 0 0 -eyeX ]   [  s0  s1  s2 -dot3(m44[0:2],  eye3) ]
53  *  [ up0 up1 up2 zzz ] * [ 0 1 0 -eyeY ] = [ up0 up1 up2 -dot3(m44[4:6],  eye3) ]
54  *  [  b0  b1  b2 zzz ]   [ 0 0 1 -eyeZ ]   [  b0  b1  b2 -dot3(m44[8:10], eye3) ]
55  *  [ zzz zzz zzz ooo ]   [ 0 0 0     1 ]   [   0   0   0                      1 ]
56 */
lookAtEyeCoordinates(float m44[],const float eye3[])57 void lookAtEyeCoordinates(float m44[], const float eye3[]) {
58     m44[3]  = -dot3(&m44[0], eye3);
59     m44[7]  = -dot3(&m44[4], eye3);
60     m44[11] = -dot3(&m44[8], eye3);
61     m44[12] = 0;
62     m44[13] = 0;
63     m44[14] = 0;
64     m44[15] = 1;
65 }
66 }  // namespace
67 
68 #define RETURN_CTOR_FAILED(S) \
69     ALOGE("%s:%s:%d %s failed", kTag, __func__, __LINE__, S); return;
70 
AutoImageKHR(const EGLDisplay display,const EGLClientBuffer clientBuf)71 AutoImageKHR::AutoImageKHR(const EGLDisplay display, const EGLClientBuffer clientBuf)
72         : mEglDisplay(display) {
73     static const EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
74     mEglImage = eglCreateImageKHR(
75         display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuf, imageAttrs);
76     if (mEglImage == EGL_NO_IMAGE_KHR) {
77         RETURN_CTOR_FAILED("eglCreateImageKHR");
78     }
79 }
80 
AutoImageKHR(AutoImageKHR && rhs)81 AutoImageKHR::AutoImageKHR(AutoImageKHR&& rhs) noexcept
82         : mEglDisplay(rhs.mEglDisplay)
83         , mEglImage(std::exchange(rhs.mEglImage, EGL_NO_IMAGE_KHR)) {}
84 
operator =(AutoImageKHR && rhs)85 AutoImageKHR& AutoImageKHR::operator=(AutoImageKHR&& rhs) noexcept {
86     if (this != &rhs) {
87         mEglDisplay = rhs.mEglDisplay;
88         mEglImage = std::exchange(rhs.mEglImage, EGL_NO_IMAGE_KHR);
89     }
90     return *this;
91 }
92 
~AutoImageKHR()93 AutoImageKHR::~AutoImageKHR() {
94     if (mEglImage != EGL_NO_IMAGE_KHR) {
95         eglDestroyImageKHR(mEglDisplay, mEglImage);
96     }
97 }
98 
EglCurrentContext(const EGLDisplay display)99 EglCurrentContext::EglCurrentContext(const EGLDisplay display)
100         : mEglDisplay(display) {}
101 
EglCurrentContext(EglCurrentContext && rhs)102 EglCurrentContext::EglCurrentContext(EglCurrentContext&& rhs) noexcept
103         : mEglDisplay(std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY)) {}
104 
operator =(EglCurrentContext && rhs)105 EglCurrentContext& EglCurrentContext::operator=(EglCurrentContext&& rhs) noexcept {
106     if (this != &rhs) {
107         mEglDisplay = std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY);
108     }
109     return *this;
110 }
111 
~EglCurrentContext()112 EglCurrentContext::~EglCurrentContext() {
113     if (mEglDisplay != EGL_NO_DISPLAY) {
114         LOG_ALWAYS_FATAL_IF(!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
115                                             EGL_NO_SURFACE, EGL_NO_CONTEXT));
116     }
117 }
118 
EglContext(EglContext && rhs)119 EglContext::EglContext(EglContext&& rhs) noexcept
120         : mEglDisplay(std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY))
121         , mEglContext(std::exchange(rhs.mEglContext, EGL_NO_CONTEXT))
122         , mEglSurface(std::exchange(rhs.mEglSurface, EGL_NO_SURFACE)) {}
123 
operator =(EglContext && rhs)124 EglContext& EglContext::operator=(EglContext&& rhs) noexcept {
125     if (this != &rhs) {
126         mEglDisplay = std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY);
127         mEglContext = std::exchange(rhs.mEglContext, EGL_NO_CONTEXT);
128         mEglSurface = std::exchange(rhs.mEglSurface, EGL_NO_SURFACE);
129     }
130     return *this;
131 }
132 
~EglContext()133 EglContext::~EglContext() {
134     clear();
135 }
136 
clear()137 void EglContext::clear() {
138     if (mEglSurface != EGL_NO_SURFACE) {
139         eglDestroySurface(mEglDisplay, mEglSurface);
140         mEglSurface = EGL_NO_SURFACE;
141     }
142     if (mEglContext != EGL_NO_CONTEXT) {
143         eglDestroyContext(mEglDisplay, mEglContext);
144         mEglContext = EGL_NO_CONTEXT;
145     }
146     if (mEglDisplay != EGL_NO_DISPLAY) {
147         eglTerminate(mEglDisplay);
148         mEglDisplay = EGL_NO_DISPLAY;
149     }
150 }
151 
init()152 EglCurrentContext EglContext::init() {
153     if (mEglContext != EGL_NO_CONTEXT) {
154         LOG_ALWAYS_FATAL_IF(!eglMakeCurrent(mEglDisplay, mEglSurface,
155                                             mEglSurface, mEglContext));
156         return EglCurrentContext(mEglDisplay);
157     }
158 
159     const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
160     if (display == EGL_NO_DISPLAY) {
161         return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
162     }
163 
164     EGLint major, minor;
165     if (!eglInitialize(display, &major, &minor)) {
166         return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
167     }
168     ALOGD("%s:%d: Initialized EGL, version %d.%d", __func__, __LINE__,
169           static_cast<int>(major), static_cast<int>(minor));
170 
171     static const EGLint configAttrs[] = {
172         EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
173         EGL_CONFIG_CAVEAT,      EGL_NONE,
174         EGL_RED_SIZE,           8,
175         EGL_GREEN_SIZE,         8,
176         EGL_BLUE_SIZE,          8,
177         EGL_ALPHA_SIZE,         8,
178         EGL_NONE
179     };
180 
181     EGLint numConfigs = 1;
182     EGLConfig config = EGL_NO_CONFIG_KHR;
183     if (!eglChooseConfig(display, configAttrs, &config, 1, &numConfigs) ||
184             (config == EGL_NO_CONFIG_KHR) || (numConfigs != 1)) {
185         eglTerminate(display);
186         return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
187     }
188 
189     static const EGLint contextAttrs[] = {
190         EGL_CONTEXT_CLIENT_VERSION, 2,
191         EGL_NONE
192     };
193     const EGLContext context = eglCreateContext(display, config,
194                                                 EGL_NO_CONTEXT, contextAttrs);
195     if (context == EGL_NO_CONTEXT) {
196         eglTerminate(display);
197         return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
198     }
199 
200     EGLSurface surface = EGL_NO_SURFACE;
201     if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)) {
202         // EGL_KHR_surfaceless_context is not supported
203         const EGLint surfaceAttrs[] = {
204             EGL_WIDTH,  1,
205             EGL_HEIGHT, 1,
206             EGL_NONE
207         };
208         surface = eglCreatePbufferSurface(display, config, surfaceAttrs);
209         if (surface == EGL_NO_SURFACE) {
210             eglDestroyContext(display, context);
211             eglTerminate(display);
212             return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
213         }
214 
215         if (!eglMakeCurrent(display, surface, surface, context)) {
216             eglDestroySurface(display, surface);
217             eglDestroyContext(display, context);
218             eglTerminate(display);
219             return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
220         }
221     }
222 
223     mEglDisplay = display;
224     mEglContext = context;
225     mEglSurface = surface;
226 
227     return EglCurrentContext(display);
228 }
229 
getCurrentContext()230 EglCurrentContext EglContext::getCurrentContext() {
231     if (mEglContext == EGL_NO_CONTEXT) {
232         return EglCurrentContext(EGL_NO_DISPLAY);
233     } else if (eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
234         return EglCurrentContext(mEglDisplay);
235     } else {
236         return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
237     }
238 }
239 
AutoTexture(const GLenum target)240 AutoTexture::AutoTexture(const GLenum target) {
241     glGenTextures(1, &mTex);
242     if (mTex) {
243         glBindTexture(target, mTex);
244     } else {
245         RETURN_CTOR_FAILED("glGenTextures");
246     }
247 }
248 
AutoTexture(const GLenum target,const GLint internalformat,const GLsizei width,const GLsizei height,const GLenum format,const GLenum type,const void * data)249 AutoTexture::AutoTexture(const GLenum target,
250                          const GLint internalformat,
251                          const GLsizei width,
252                          const GLsizei height,
253                          const GLenum format,
254                          const GLenum type,
255                          const void* data) {
256     glGenTextures(1, &mTex);
257     if (mTex) {
258         glBindTexture(target, mTex);
259         glTexImage2D(target, 0, internalformat, width, height, 0, format, type, data);
260     } else {
261         RETURN_CTOR_FAILED("glGenTextures");
262     }
263 }
264 
AutoTexture(AutoTexture && rhs)265 AutoTexture::AutoTexture(AutoTexture&& rhs) noexcept
266         : mTex(std::exchange(rhs.mTex, 0)) {}
267 
operator =(AutoTexture && rhs)268 AutoTexture& AutoTexture::operator=(AutoTexture&& rhs) noexcept {
269     if (this != &rhs) {
270         mTex = std::exchange(rhs.mTex, 0);
271     }
272     return *this;
273 }
274 
~AutoTexture()275 AutoTexture::~AutoTexture() {
276     clear();
277 }
278 
clear()279 void AutoTexture::clear() {
280     if (mTex) {
281         glDeleteTextures(1, &mTex);
282         mTex = 0;
283     }
284 }
285 
AutoFrameBuffer()286 AutoFrameBuffer::AutoFrameBuffer() {
287     glGenFramebuffers(1, &mFBO);
288     if (mFBO) {
289         glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
290     } else {
291         RETURN_CTOR_FAILED("glGenFramebuffers");
292     }
293 }
294 
~AutoFrameBuffer()295 AutoFrameBuffer::~AutoFrameBuffer() {
296     if (mFBO) {
297         glDeleteFramebuffers(1, &mFBO);
298     }
299 }
300 
AutoShader(AutoShader && rhs)301 AutoShader::AutoShader(AutoShader&& rhs) noexcept
302         : mShader(std::exchange(rhs.mShader, 0)) {}
303 
operator =(AutoShader && rhs)304 AutoShader& AutoShader::operator=(AutoShader&& rhs) noexcept {
305     if (this != &rhs) {
306         mShader = std::exchange(rhs.mShader, 0);
307     }
308     return *this;
309 }
310 
~AutoShader()311 AutoShader::~AutoShader() {
312     if (mShader) {
313         glDeleteShader(mShader);
314     }
315 }
316 
compile(const GLenum type,const char * text)317 GLuint AutoShader::compile(const GLenum type, const char* text) {
318     const GLuint shader = glCreateShader(type);
319     if (!shader) {
320         return FAILURE(0);
321     }
322 
323     glShaderSource(shader, 1, &text, nullptr);
324     glCompileShader(shader);
325     GLint compiled = 0;
326     glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
327     if (!compiled) {
328         GLint infoLen = 0;
329         glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
330         if(infoLen > 1) {
331             std::vector<char> msg(infoLen + 1);
332             glGetShaderInfoLog(shader, infoLen, nullptr, msg.data());
333             msg[infoLen] = 0;
334             ALOGE("%s:%d: error compiling shader '%s' (type=%d): '%s'",
335                   __func__, __LINE__, text, type, msg.data());
336         }
337         glDeleteShader(shader);
338         return FAILURE(0);
339     }
340 
341     if (mShader) {
342         glDeleteShader(mShader);
343     }
344 
345     mShader = shader;
346     return shader;
347 }
348 
AutoProgram(AutoProgram && rhs)349 AutoProgram::AutoProgram(AutoProgram&& rhs) noexcept
350         : mProgram(std::exchange(rhs.mProgram, 0)) {}
351 
operator =(AutoProgram && rhs)352 AutoProgram& AutoProgram::operator=(AutoProgram&& rhs) noexcept {
353     if (this != &rhs) {
354         mProgram = std::exchange(rhs.mProgram, 0);
355     }
356     return *this;
357 }
358 
~AutoProgram()359 AutoProgram::~AutoProgram() {
360     clear();
361 }
362 
clear()363 void AutoProgram::clear() {
364     if (mProgram) {
365         glDeleteProgram(mProgram);
366         mProgram = 0;
367     }
368 }
369 
link(const GLuint vertexShader,const GLuint fragmentShader)370 bool AutoProgram::link(const GLuint vertexShader,
371                        const GLuint fragmentShader) {
372     const GLuint program = glCreateProgram();
373     if (!program) {
374         return FAILURE(false);
375     }
376 
377     glAttachShader(program, vertexShader);
378     glAttachShader(program, fragmentShader);
379     glLinkProgram(program);
380 
381     GLint linked = 0;
382     glGetProgramiv(program, GL_LINK_STATUS, &linked);
383     if (!linked) {
384         GLint infoLen = 0;
385         glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
386         if(infoLen > 1) {
387             std::vector<char> msg(infoLen + 1);
388             glGetProgramInfoLog(program, infoLen, nullptr, msg.data());
389             msg[infoLen] = 0;
390             ALOGE("%s:%d: error linking shaders: '%s'",
391                   __func__, __LINE__, msg.data());
392         }
393 
394         glDeleteProgram(program);
395         return FAILURE(false);
396     }
397 
398     if (mProgram) {
399         glDeleteProgram(mProgram);
400     }
401 
402     mProgram = program;
403     return true;
404 }
405 
getAttribLocation(const char * name) const406 GLint AutoProgram::getAttribLocation(const char* name) const {
407     if (mProgram > 0) {
408         const GLint result = glGetAttribLocation(mProgram, name);
409         return (result >= 0) ? result : FAILURE(-1);
410     } else {
411         return FAILURE(-1);
412     }
413 }
414 
getUniformLocation(const char * name) const415 GLint AutoProgram::getUniformLocation(const char* name) const {
416     if (mProgram > 0) {
417         const GLint result = glGetUniformLocation(mProgram, name);
418         return (result >= 0) ? result : FAILURE(-1);
419     } else {
420         return FAILURE(-1);
421     }
422 }
423 
424 // https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
frustum(float m44[],const double left,const double right,const double bottom,const double top,const double near,const double far)425 void frustum(float m44[],
426              const double left, const double right,
427              const double bottom, const double top,
428              const double near, const double far) {
429     const double invWidth = 1.0 / (right - left);
430     const double invHeight = 1.0 / (top - bottom);
431     const double invDepth = 1.0 / (far - near);
432     const double near2 = 2 * near;
433 
434     m44[0] = near2 * invWidth;
435     m44[1] = 0;
436     m44[2] = (right + left) * invWidth;
437     m44[3] = 0;
438 
439     m44[4] = 0;
440     m44[5] = near2 * invHeight;
441     m44[6] = (top + bottom) * invHeight;
442     m44[7] = 0;
443 
444     m44[8] = 0;
445     m44[9] = 0;
446     m44[10] = -(far + near) * invDepth;
447     m44[11] = -far * near2 * invDepth;
448 
449     m44[12] = 0;
450     m44[13] = 0;
451     m44[14] = -1;
452     m44[15] = 0;
453 }
454 
455 /*
456  * https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
457  * https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
458  *
459  * Here we calculate {Side, Up, Backwards} from Euler angles in the XYZ order:
460  *
461  * [ 1,    0,     0 ]   [  cosY, 0, sinY ]   [ cosZ, -sinZ, 0 ]   [ sx, ux, bx ]
462  * [ 0, cosX, -sinX ] * [     0, 1,    0 ] * [ sinZ,  cosZ, 0 ] = [ sy, uy, by ]
463  * [ 0, sinX,  cosX ]   [ -sinY, 0, cosY ]   [    0,     0, 1 ]   [ sz, uz, bz ]
464  *
465  * We calculate `backwards` because the camera looks into the negative Z
466  * direction, so instead of calculating camera's forward and negating it twice,
467  * let's call it `backwards`.
468  *
469  * After multiplying the first two:
470  * [         cosY,    0,         sinY ]
471  * [  sinX * sinY, cosX, -sinX * cosY ]
472  * [ -cosX * sinY, sinX,  cosX * cosY ]
473  *
474  * The final result:
475  * [                       cosY * cosZ,                      -cosY * sinZ,         sinY ]
476  * [  sinX * sinY * cosZ + cosX * sinZ, -sinX * sinY * sinZ + cosX * cosZ, -sinX * cosY ]
477  * [ -cosX * sinY * cosZ + sinX * sinZ,  cosX * sinY * sinZ + sinX * cosZ,  cosX * cosY ]
478  *
479  * {Side, Up, Backwards} are the columns in the matrix above.
480  */
lookAtXyzRot(float m44[],const float eye3[],const float rot3[])481 void lookAtXyzRot(float m44[], const float eye3[], const float rot3[]) {
482     const double sinX = sin(rot3[0]);
483     const double cosX = cos(rot3[0]);
484     const double sinY = sin(rot3[1]);
485     const double cosY = cos(rot3[1]);
486     const double sinZ = sin(rot3[2]);
487     const double cosZ = cos(rot3[2]);
488 
489     m44[0]  = cosY * cosZ;
490     m44[1]  = sinX * sinY * cosZ + cosX * sinZ;
491     m44[2]  = -cosX * sinY * cosZ + sinX * sinZ;
492     m44[4]  = -cosY * sinZ;
493     m44[5]  = -sinX * sinY * sinZ + cosX * cosZ;
494     m44[6]  = cosX * sinY * sinZ + sinX * cosZ;
495     m44[8]  = sinY;
496     m44[9]  = -sinX * cosY;
497     m44[10] = cosX * cosY;
498     lookAtEyeCoordinates(m44, eye3);
499 }
500 
mulM44(float m44[],const float lhs44[],const float rhs44[])501 void mulM44(float m44[], const float lhs44[], const float rhs44[]) {
502     m44[0] = lhs44[0] * rhs44[0] + lhs44[1] * rhs44[4] + lhs44[2] * rhs44[8] + lhs44[3] * rhs44[12];
503     m44[1] = lhs44[0] * rhs44[1] + lhs44[1] * rhs44[5] + lhs44[2] * rhs44[9] + lhs44[3] * rhs44[13];
504     m44[2] = lhs44[0] * rhs44[2] + lhs44[1] * rhs44[6] + lhs44[2] * rhs44[10] + lhs44[3] * rhs44[14];
505     m44[3] = lhs44[0] * rhs44[3] + lhs44[1] * rhs44[7] + lhs44[2] * rhs44[11] + lhs44[3] * rhs44[15];
506 
507     m44[4] = lhs44[4] * rhs44[0] + lhs44[5] * rhs44[4] + lhs44[6] * rhs44[8] + lhs44[7] * rhs44[12];
508     m44[5] = lhs44[4] * rhs44[1] + lhs44[5] * rhs44[5] + lhs44[6] * rhs44[9] + lhs44[7] * rhs44[13];
509     m44[6] = lhs44[4] * rhs44[2] + lhs44[5] * rhs44[6] + lhs44[6] * rhs44[10] + lhs44[7] * rhs44[14];
510     m44[7] = lhs44[4] * rhs44[3] + lhs44[5] * rhs44[7] + lhs44[6] * rhs44[11] + lhs44[7] * rhs44[15];
511 
512     m44[8] = lhs44[8] * rhs44[0] + lhs44[9] * rhs44[4] + lhs44[10] * rhs44[8] + lhs44[11] * rhs44[12];
513     m44[9] = lhs44[8] * rhs44[1] + lhs44[9] * rhs44[5] + lhs44[10] * rhs44[9] + lhs44[11] * rhs44[13];
514     m44[10] = lhs44[8] * rhs44[2] + lhs44[9] * rhs44[6] + lhs44[10] * rhs44[10] + lhs44[11] * rhs44[14];
515     m44[11] = lhs44[8] * rhs44[3] + lhs44[9] * rhs44[7] + lhs44[10] * rhs44[11] + lhs44[11] * rhs44[15];
516 
517     m44[12] = lhs44[12] * rhs44[0] + lhs44[13] * rhs44[4] + lhs44[14] * rhs44[8] + lhs44[15] * rhs44[12];
518     m44[13] = lhs44[12] * rhs44[1] + lhs44[13] * rhs44[5] + lhs44[14] * rhs44[9] + lhs44[15] * rhs44[13];
519     m44[14] = lhs44[12] * rhs44[2] + lhs44[13] * rhs44[6] + lhs44[14] * rhs44[10] + lhs44[15] * rhs44[14];
520     m44[15] = lhs44[12] * rhs44[3] + lhs44[13] * rhs44[7] + lhs44[14] * rhs44[11] + lhs44[15] * rhs44[15];
521 }
522 
523 }  // namespace abc3d
524 }  // namespace implementation
525 }  // namespace provider
526 }  // namespace camera
527 }  // namespace hardware
528 }  // namespace android
529