529 lines
17 KiB
C++
529 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2023 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <vector>
|
|
#include <math.h>
|
|
|
|
#define GL_GLEXT_PROTOTYPES
|
|
#define EGL_EGLEXT_PROTOTYPES
|
|
#include <EGL/egl.h>
|
|
#include <EGL/eglext.h>
|
|
#include <GLES2/gl2.h>
|
|
#include <GLES2/gl2ext.h>
|
|
#undef EGL_EGLEXT_PROTOTYPES
|
|
#undef GL_GLEXT_PROTOTYPES
|
|
|
|
#include "abc3d.h"
|
|
#include "debug.h"
|
|
|
|
namespace android {
|
|
namespace hardware {
|
|
namespace camera {
|
|
namespace provider {
|
|
namespace implementation {
|
|
namespace abc3d {
|
|
namespace {
|
|
constexpr char kTag[] = "abc3d";
|
|
|
|
float dot3(const float a3[], const float b3[]) {
|
|
return a3[0] * b3[0] + a3[1] * b3[1] + a3[2] * b3[2];
|
|
}
|
|
|
|
/*
|
|
* https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
|
|
* https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml
|
|
* This function takes `m44` (where zzz (assumed zero) and ooo (assumed one)
|
|
* are ignored) and multiplies it by a translation matrix.
|
|
*
|
|
* m44 translate
|
|
* [ s0 s1 s2 zzz ] [ 1 0 0 -eyeX ] [ s0 s1 s2 -dot3(m44[0:2], eye3) ]
|
|
* [ up0 up1 up2 zzz ] * [ 0 1 0 -eyeY ] = [ up0 up1 up2 -dot3(m44[4:6], eye3) ]
|
|
* [ b0 b1 b2 zzz ] [ 0 0 1 -eyeZ ] [ b0 b1 b2 -dot3(m44[8:10], eye3) ]
|
|
* [ zzz zzz zzz ooo ] [ 0 0 0 1 ] [ 0 0 0 1 ]
|
|
*/
|
|
void lookAtEyeCoordinates(float m44[], const float eye3[]) {
|
|
m44[3] = -dot3(&m44[0], eye3);
|
|
m44[7] = -dot3(&m44[4], eye3);
|
|
m44[11] = -dot3(&m44[8], eye3);
|
|
m44[12] = 0;
|
|
m44[13] = 0;
|
|
m44[14] = 0;
|
|
m44[15] = 1;
|
|
}
|
|
} // namespace
|
|
|
|
#define RETURN_CTOR_FAILED(S) \
|
|
ALOGE("%s:%s:%d %s failed", kTag, __func__, __LINE__, S); return;
|
|
|
|
AutoImageKHR::AutoImageKHR(const EGLDisplay display, const EGLClientBuffer clientBuf)
|
|
: mEglDisplay(display) {
|
|
static const EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
|
|
mEglImage = eglCreateImageKHR(
|
|
display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuf, imageAttrs);
|
|
if (mEglImage == EGL_NO_IMAGE_KHR) {
|
|
RETURN_CTOR_FAILED("eglCreateImageKHR");
|
|
}
|
|
}
|
|
|
|
AutoImageKHR::AutoImageKHR(AutoImageKHR&& rhs) noexcept
|
|
: mEglDisplay(rhs.mEglDisplay)
|
|
, mEglImage(std::exchange(rhs.mEglImage, EGL_NO_IMAGE_KHR)) {}
|
|
|
|
AutoImageKHR& AutoImageKHR::operator=(AutoImageKHR&& rhs) noexcept {
|
|
if (this != &rhs) {
|
|
mEglDisplay = rhs.mEglDisplay;
|
|
mEglImage = std::exchange(rhs.mEglImage, EGL_NO_IMAGE_KHR);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
AutoImageKHR::~AutoImageKHR() {
|
|
if (mEglImage != EGL_NO_IMAGE_KHR) {
|
|
eglDestroyImageKHR(mEglDisplay, mEglImage);
|
|
}
|
|
}
|
|
|
|
EglCurrentContext::EglCurrentContext(const EGLDisplay display)
|
|
: mEglDisplay(display) {}
|
|
|
|
EglCurrentContext::EglCurrentContext(EglCurrentContext&& rhs) noexcept
|
|
: mEglDisplay(std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY)) {}
|
|
|
|
EglCurrentContext& EglCurrentContext::operator=(EglCurrentContext&& rhs) noexcept {
|
|
if (this != &rhs) {
|
|
mEglDisplay = std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
EglCurrentContext::~EglCurrentContext() {
|
|
if (mEglDisplay != EGL_NO_DISPLAY) {
|
|
LOG_ALWAYS_FATAL_IF(!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
|
|
EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
|
}
|
|
}
|
|
|
|
EglContext::EglContext(EglContext&& rhs) noexcept
|
|
: mEglDisplay(std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY))
|
|
, mEglContext(std::exchange(rhs.mEglContext, EGL_NO_CONTEXT))
|
|
, mEglSurface(std::exchange(rhs.mEglSurface, EGL_NO_SURFACE)) {}
|
|
|
|
EglContext& EglContext::operator=(EglContext&& rhs) noexcept {
|
|
if (this != &rhs) {
|
|
mEglDisplay = std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY);
|
|
mEglContext = std::exchange(rhs.mEglContext, EGL_NO_CONTEXT);
|
|
mEglSurface = std::exchange(rhs.mEglSurface, EGL_NO_SURFACE);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
EglContext::~EglContext() {
|
|
clear();
|
|
}
|
|
|
|
void EglContext::clear() {
|
|
if (mEglSurface != EGL_NO_SURFACE) {
|
|
eglDestroySurface(mEglDisplay, mEglSurface);
|
|
mEglSurface = EGL_NO_SURFACE;
|
|
}
|
|
if (mEglContext != EGL_NO_CONTEXT) {
|
|
eglDestroyContext(mEglDisplay, mEglContext);
|
|
mEglContext = EGL_NO_CONTEXT;
|
|
}
|
|
if (mEglDisplay != EGL_NO_DISPLAY) {
|
|
eglTerminate(mEglDisplay);
|
|
mEglDisplay = EGL_NO_DISPLAY;
|
|
}
|
|
}
|
|
|
|
EglCurrentContext EglContext::init() {
|
|
if (mEglContext != EGL_NO_CONTEXT) {
|
|
LOG_ALWAYS_FATAL_IF(!eglMakeCurrent(mEglDisplay, mEglSurface,
|
|
mEglSurface, mEglContext));
|
|
return EglCurrentContext(mEglDisplay);
|
|
}
|
|
|
|
const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if (display == EGL_NO_DISPLAY) {
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
|
|
EGLint major, minor;
|
|
if (!eglInitialize(display, &major, &minor)) {
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
ALOGD("%s:%d: Initialized EGL, version %d.%d", __func__, __LINE__,
|
|
static_cast<int>(major), static_cast<int>(minor));
|
|
|
|
static const EGLint configAttrs[] = {
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
|
EGL_CONFIG_CAVEAT, EGL_NONE,
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_ALPHA_SIZE, 8,
|
|
EGL_NONE
|
|
};
|
|
|
|
EGLint numConfigs = 1;
|
|
EGLConfig config = EGL_NO_CONFIG_KHR;
|
|
if (!eglChooseConfig(display, configAttrs, &config, 1, &numConfigs) ||
|
|
(config == EGL_NO_CONFIG_KHR) || (numConfigs != 1)) {
|
|
eglTerminate(display);
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
|
|
static const EGLint contextAttrs[] = {
|
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL_NONE
|
|
};
|
|
const EGLContext context = eglCreateContext(display, config,
|
|
EGL_NO_CONTEXT, contextAttrs);
|
|
if (context == EGL_NO_CONTEXT) {
|
|
eglTerminate(display);
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
|
|
EGLSurface surface = EGL_NO_SURFACE;
|
|
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)) {
|
|
// EGL_KHR_surfaceless_context is not supported
|
|
const EGLint surfaceAttrs[] = {
|
|
EGL_WIDTH, 1,
|
|
EGL_HEIGHT, 1,
|
|
EGL_NONE
|
|
};
|
|
surface = eglCreatePbufferSurface(display, config, surfaceAttrs);
|
|
if (surface == EGL_NO_SURFACE) {
|
|
eglDestroyContext(display, context);
|
|
eglTerminate(display);
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
|
|
if (!eglMakeCurrent(display, surface, surface, context)) {
|
|
eglDestroySurface(display, surface);
|
|
eglDestroyContext(display, context);
|
|
eglTerminate(display);
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
}
|
|
|
|
mEglDisplay = display;
|
|
mEglContext = context;
|
|
mEglSurface = surface;
|
|
|
|
return EglCurrentContext(display);
|
|
}
|
|
|
|
EglCurrentContext EglContext::getCurrentContext() {
|
|
if (mEglContext == EGL_NO_CONTEXT) {
|
|
return EglCurrentContext(EGL_NO_DISPLAY);
|
|
} else if (eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
|
|
return EglCurrentContext(mEglDisplay);
|
|
} else {
|
|
return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
|
|
}
|
|
}
|
|
|
|
AutoTexture::AutoTexture(const GLenum target) {
|
|
glGenTextures(1, &mTex);
|
|
if (mTex) {
|
|
glBindTexture(target, mTex);
|
|
} else {
|
|
RETURN_CTOR_FAILED("glGenTextures");
|
|
}
|
|
}
|
|
|
|
AutoTexture::AutoTexture(const GLenum target,
|
|
const GLint internalformat,
|
|
const GLsizei width,
|
|
const GLsizei height,
|
|
const GLenum format,
|
|
const GLenum type,
|
|
const void* data) {
|
|
glGenTextures(1, &mTex);
|
|
if (mTex) {
|
|
glBindTexture(target, mTex);
|
|
glTexImage2D(target, 0, internalformat, width, height, 0, format, type, data);
|
|
} else {
|
|
RETURN_CTOR_FAILED("glGenTextures");
|
|
}
|
|
}
|
|
|
|
AutoTexture::AutoTexture(AutoTexture&& rhs) noexcept
|
|
: mTex(std::exchange(rhs.mTex, 0)) {}
|
|
|
|
AutoTexture& AutoTexture::operator=(AutoTexture&& rhs) noexcept {
|
|
if (this != &rhs) {
|
|
mTex = std::exchange(rhs.mTex, 0);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
AutoTexture::~AutoTexture() {
|
|
clear();
|
|
}
|
|
|
|
void AutoTexture::clear() {
|
|
if (mTex) {
|
|
glDeleteTextures(1, &mTex);
|
|
mTex = 0;
|
|
}
|
|
}
|
|
|
|
AutoFrameBuffer::AutoFrameBuffer() {
|
|
glGenFramebuffers(1, &mFBO);
|
|
if (mFBO) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
|
|
} else {
|
|
RETURN_CTOR_FAILED("glGenFramebuffers");
|
|
}
|
|
}
|
|
|
|
AutoFrameBuffer::~AutoFrameBuffer() {
|
|
if (mFBO) {
|
|
glDeleteFramebuffers(1, &mFBO);
|
|
}
|
|
}
|
|
|
|
AutoShader::AutoShader(AutoShader&& rhs) noexcept
|
|
: mShader(std::exchange(rhs.mShader, 0)) {}
|
|
|
|
AutoShader& AutoShader::operator=(AutoShader&& rhs) noexcept {
|
|
if (this != &rhs) {
|
|
mShader = std::exchange(rhs.mShader, 0);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
AutoShader::~AutoShader() {
|
|
if (mShader) {
|
|
glDeleteShader(mShader);
|
|
}
|
|
}
|
|
|
|
GLuint AutoShader::compile(const GLenum type, const char* text) {
|
|
const GLuint shader = glCreateShader(type);
|
|
if (!shader) {
|
|
return FAILURE(0);
|
|
}
|
|
|
|
glShaderSource(shader, 1, &text, nullptr);
|
|
glCompileShader(shader);
|
|
GLint compiled = 0;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
|
if (!compiled) {
|
|
GLint infoLen = 0;
|
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
|
|
if(infoLen > 1) {
|
|
std::vector<char> msg(infoLen + 1);
|
|
glGetShaderInfoLog(shader, infoLen, nullptr, msg.data());
|
|
msg[infoLen] = 0;
|
|
ALOGE("%s:%d: error compiling shader '%s' (type=%d): '%s'",
|
|
__func__, __LINE__, text, type, msg.data());
|
|
}
|
|
glDeleteShader(shader);
|
|
return FAILURE(0);
|
|
}
|
|
|
|
if (mShader) {
|
|
glDeleteShader(mShader);
|
|
}
|
|
|
|
mShader = shader;
|
|
return shader;
|
|
}
|
|
|
|
AutoProgram::AutoProgram(AutoProgram&& rhs) noexcept
|
|
: mProgram(std::exchange(rhs.mProgram, 0)) {}
|
|
|
|
AutoProgram& AutoProgram::operator=(AutoProgram&& rhs) noexcept {
|
|
if (this != &rhs) {
|
|
mProgram = std::exchange(rhs.mProgram, 0);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
AutoProgram::~AutoProgram() {
|
|
clear();
|
|
}
|
|
|
|
void AutoProgram::clear() {
|
|
if (mProgram) {
|
|
glDeleteProgram(mProgram);
|
|
mProgram = 0;
|
|
}
|
|
}
|
|
|
|
bool AutoProgram::link(const GLuint vertexShader,
|
|
const GLuint fragmentShader) {
|
|
const GLuint program = glCreateProgram();
|
|
if (!program) {
|
|
return FAILURE(false);
|
|
}
|
|
|
|
glAttachShader(program, vertexShader);
|
|
glAttachShader(program, fragmentShader);
|
|
glLinkProgram(program);
|
|
|
|
GLint linked = 0;
|
|
glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
|
if (!linked) {
|
|
GLint infoLen = 0;
|
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
|
|
if(infoLen > 1) {
|
|
std::vector<char> msg(infoLen + 1);
|
|
glGetProgramInfoLog(program, infoLen, nullptr, msg.data());
|
|
msg[infoLen] = 0;
|
|
ALOGE("%s:%d: error linking shaders: '%s'",
|
|
__func__, __LINE__, msg.data());
|
|
}
|
|
|
|
glDeleteProgram(program);
|
|
return FAILURE(false);
|
|
}
|
|
|
|
if (mProgram) {
|
|
glDeleteProgram(mProgram);
|
|
}
|
|
|
|
mProgram = program;
|
|
return true;
|
|
}
|
|
|
|
GLint AutoProgram::getAttribLocation(const char* name) const {
|
|
if (mProgram > 0) {
|
|
const GLint result = glGetAttribLocation(mProgram, name);
|
|
return (result >= 0) ? result : FAILURE(-1);
|
|
} else {
|
|
return FAILURE(-1);
|
|
}
|
|
}
|
|
|
|
GLint AutoProgram::getUniformLocation(const char* name) const {
|
|
if (mProgram > 0) {
|
|
const GLint result = glGetUniformLocation(mProgram, name);
|
|
return (result >= 0) ? result : FAILURE(-1);
|
|
} else {
|
|
return FAILURE(-1);
|
|
}
|
|
}
|
|
|
|
// https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
|
|
void frustum(float m44[],
|
|
const double left, const double right,
|
|
const double bottom, const double top,
|
|
const double near, const double far) {
|
|
const double invWidth = 1.0 / (right - left);
|
|
const double invHeight = 1.0 / (top - bottom);
|
|
const double invDepth = 1.0 / (far - near);
|
|
const double near2 = 2 * near;
|
|
|
|
m44[0] = near2 * invWidth;
|
|
m44[1] = 0;
|
|
m44[2] = (right + left) * invWidth;
|
|
m44[3] = 0;
|
|
|
|
m44[4] = 0;
|
|
m44[5] = near2 * invHeight;
|
|
m44[6] = (top + bottom) * invHeight;
|
|
m44[7] = 0;
|
|
|
|
m44[8] = 0;
|
|
m44[9] = 0;
|
|
m44[10] = -(far + near) * invDepth;
|
|
m44[11] = -far * near2 * invDepth;
|
|
|
|
m44[12] = 0;
|
|
m44[13] = 0;
|
|
m44[14] = -1;
|
|
m44[15] = 0;
|
|
}
|
|
|
|
/*
|
|
* https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
|
|
* https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
|
|
*
|
|
* Here we calculate {Side, Up, Backwards} from Euler angles in the XYZ order:
|
|
*
|
|
* [ 1, 0, 0 ] [ cosY, 0, sinY ] [ cosZ, -sinZ, 0 ] [ sx, ux, bx ]
|
|
* [ 0, cosX, -sinX ] * [ 0, 1, 0 ] * [ sinZ, cosZ, 0 ] = [ sy, uy, by ]
|
|
* [ 0, sinX, cosX ] [ -sinY, 0, cosY ] [ 0, 0, 1 ] [ sz, uz, bz ]
|
|
*
|
|
* We calculate `backwards` because the camera looks into the negative Z
|
|
* direction, so instead of calculating camera's forward and negating it twice,
|
|
* let's call it `backwards`.
|
|
*
|
|
* After multiplying the first two:
|
|
* [ cosY, 0, sinY ]
|
|
* [ sinX * sinY, cosX, -sinX * cosY ]
|
|
* [ -cosX * sinY, sinX, cosX * cosY ]
|
|
*
|
|
* The final result:
|
|
* [ cosY * cosZ, -cosY * sinZ, sinY ]
|
|
* [ sinX * sinY * cosZ + cosX * sinZ, -sinX * sinY * sinZ + cosX * cosZ, -sinX * cosY ]
|
|
* [ -cosX * sinY * cosZ + sinX * sinZ, cosX * sinY * sinZ + sinX * cosZ, cosX * cosY ]
|
|
*
|
|
* {Side, Up, Backwards} are the columns in the matrix above.
|
|
*/
|
|
void lookAtXyzRot(float m44[], const float eye3[], const float rot3[]) {
|
|
const double sinX = sin(rot3[0]);
|
|
const double cosX = cos(rot3[0]);
|
|
const double sinY = sin(rot3[1]);
|
|
const double cosY = cos(rot3[1]);
|
|
const double sinZ = sin(rot3[2]);
|
|
const double cosZ = cos(rot3[2]);
|
|
|
|
m44[0] = cosY * cosZ;
|
|
m44[1] = sinX * sinY * cosZ + cosX * sinZ;
|
|
m44[2] = -cosX * sinY * cosZ + sinX * sinZ;
|
|
m44[4] = -cosY * sinZ;
|
|
m44[5] = -sinX * sinY * sinZ + cosX * cosZ;
|
|
m44[6] = cosX * sinY * sinZ + sinX * cosZ;
|
|
m44[8] = sinY;
|
|
m44[9] = -sinX * cosY;
|
|
m44[10] = cosX * cosY;
|
|
lookAtEyeCoordinates(m44, eye3);
|
|
}
|
|
|
|
void mulM44(float m44[], const float lhs44[], const float rhs44[]) {
|
|
m44[0] = lhs44[0] * rhs44[0] + lhs44[1] * rhs44[4] + lhs44[2] * rhs44[8] + lhs44[3] * rhs44[12];
|
|
m44[1] = lhs44[0] * rhs44[1] + lhs44[1] * rhs44[5] + lhs44[2] * rhs44[9] + lhs44[3] * rhs44[13];
|
|
m44[2] = lhs44[0] * rhs44[2] + lhs44[1] * rhs44[6] + lhs44[2] * rhs44[10] + lhs44[3] * rhs44[14];
|
|
m44[3] = lhs44[0] * rhs44[3] + lhs44[1] * rhs44[7] + lhs44[2] * rhs44[11] + lhs44[3] * rhs44[15];
|
|
|
|
m44[4] = lhs44[4] * rhs44[0] + lhs44[5] * rhs44[4] + lhs44[6] * rhs44[8] + lhs44[7] * rhs44[12];
|
|
m44[5] = lhs44[4] * rhs44[1] + lhs44[5] * rhs44[5] + lhs44[6] * rhs44[9] + lhs44[7] * rhs44[13];
|
|
m44[6] = lhs44[4] * rhs44[2] + lhs44[5] * rhs44[6] + lhs44[6] * rhs44[10] + lhs44[7] * rhs44[14];
|
|
m44[7] = lhs44[4] * rhs44[3] + lhs44[5] * rhs44[7] + lhs44[6] * rhs44[11] + lhs44[7] * rhs44[15];
|
|
|
|
m44[8] = lhs44[8] * rhs44[0] + lhs44[9] * rhs44[4] + lhs44[10] * rhs44[8] + lhs44[11] * rhs44[12];
|
|
m44[9] = lhs44[8] * rhs44[1] + lhs44[9] * rhs44[5] + lhs44[10] * rhs44[9] + lhs44[11] * rhs44[13];
|
|
m44[10] = lhs44[8] * rhs44[2] + lhs44[9] * rhs44[6] + lhs44[10] * rhs44[10] + lhs44[11] * rhs44[14];
|
|
m44[11] = lhs44[8] * rhs44[3] + lhs44[9] * rhs44[7] + lhs44[10] * rhs44[11] + lhs44[11] * rhs44[15];
|
|
|
|
m44[12] = lhs44[12] * rhs44[0] + lhs44[13] * rhs44[4] + lhs44[14] * rhs44[8] + lhs44[15] * rhs44[12];
|
|
m44[13] = lhs44[12] * rhs44[1] + lhs44[13] * rhs44[5] + lhs44[14] * rhs44[9] + lhs44[15] * rhs44[13];
|
|
m44[14] = lhs44[12] * rhs44[2] + lhs44[13] * rhs44[6] + lhs44[14] * rhs44[10] + lhs44[15] * rhs44[14];
|
|
m44[15] = lhs44[12] * rhs44[3] + lhs44[13] * rhs44[7] + lhs44[14] * rhs44[11] + lhs44[15] * rhs44[15];
|
|
}
|
|
|
|
} // namespace abc3d
|
|
} // namespace implementation
|
|
} // namespace provider
|
|
} // namespace camera
|
|
} // namespace hardware
|
|
} // namespace android
|