blob: be79d71c7379bfb656769ef68fb619d0f521c0b5 [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
package com.android.car.internal.evs;
import static android.opengl.GLU.gluErrorString;
import android.annotation.NonNull;
import android.car.evs.CarEvsBufferDescriptor;
import android.hardware.HardwareBuffer;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* GLES20 SurfaceView Renderer for CarEvsBufferDescriptor.
*/
public final class GLES20CarEvsBufferRenderer implements GLSurfaceView.Renderer {
private static final String TAG = GLES20CarEvsBufferRenderer.class.getSimpleName()
.replace("GLES20", "");
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final int FLOAT_SIZE_BYTES = 4;
private static final float[] sVertPosData = {
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f };
private static final float[] sVertTexData = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f };
private static final float[] sIdentityMatrix = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f };
private static final String mVertexShader =
"attribute vec4 pos; \n" +
"attribute vec2 tex; \n" +
"uniform mat4 cameraMat; \n" +
"varying vec2 uv; \n" +
"void main() \n" +
"{ \n" +
" gl_Position = cameraMat * pos; \n" +
" uv = tex; \n" +
"} \n";
private static final String mFragmentShader =
"precision mediump float; \n" +
"uniform sampler2D tex; \n" +
"varying vec2 uv; \n" +
"void main() \n" +
"{ \n" +
" gl_FragColor = texture2D(tex, uv); \n" +
"} \n";
private static final int INDEX_TO_X_STRIDE = 0;
private static final int INDEX_TO_Y_STRIDE = 1;
private final Object mLock = new Object();
private final ArrayList<CarEvsGLSurfaceView.BufferCallback> mCallbacks;
private final FloatBuffer mVertPos;
private final FloatBuffer mVertTex;
private final int mTextureId[];
private final float mPositions[][];
private int mProgram;
private int mWidth;
private int mHeight;
// Hold buffers currently in use.
@GuardedBy("mLock")
private final CarEvsBufferDescriptor mBufferInUse[];
/** Load jni on initialization. */
static {
System.loadLibrary("carevsglrenderer_jni");
}
public GLES20CarEvsBufferRenderer(ArrayList<CarEvsGLSurfaceView.BufferCallback> callbacks,
int angleInDegree, float[][] positions) {
Preconditions.checkArgument(callbacks != null, "Callback cannot be null.");
Preconditions.checkArgument(callbacks.size() <= positions.length,
"At least " + callbacks.size() + " positions are needed.");
mCallbacks = callbacks;
mTextureId = new int[callbacks.size()];
mPositions = positions;
mBufferInUse = new CarEvsBufferDescriptor[callbacks.size()];
mVertPos = ByteBuffer.allocateDirect(sVertPosData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertPos.put(sVertPosData).position(0);
double angleInRadian = Math.toRadians(angleInDegree);
float[] rotated = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
float sin = (float)Math.sin(angleInRadian);
float cos = (float)Math.cos(angleInRadian);
rotated[0] += cos * sVertTexData[0] - sin * sVertTexData[1];
rotated[1] += sin * sVertTexData[0] + cos * sVertTexData[1];
rotated[2] += cos * sVertTexData[2] - sin * sVertTexData[3];
rotated[3] += sin * sVertTexData[2] + cos * sVertTexData[3];
rotated[4] += cos * sVertTexData[4] - sin * sVertTexData[5];
rotated[5] += sin * sVertTexData[4] + cos * sVertTexData[5];
rotated[6] += cos * sVertTexData[6] - sin * sVertTexData[7];
rotated[7] += sin * sVertTexData[6] + cos * sVertTexData[7];
mVertTex = ByteBuffer.allocateDirect(sVertTexData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertTex.put(rotated).position(0);
}
public void clearBuffer() {
for (int i = 0; i < mCallbacks.size(); i++) {
CarEvsBufferDescriptor bufferToReturn = null;
synchronized (mLock) {
if (mBufferInUse[i] == null) {
continue;
}
bufferToReturn = mBufferInUse[i];
mBufferInUse[i] = null;
}
// bufferToReturn is not null here.
mCallbacks.get(i).onBufferProcessed(bufferToReturn);
}
}
@Override
public void onDrawFrame(GL10 glUnused) {
// Use the GLES20 class's static methods instead of a passed GL10 interface.
for (int i = 0; i < mCallbacks.size(); i++) {
CarEvsBufferDescriptor bufferToRender = null;
CarEvsBufferDescriptor bufferToReturn = null;
CarEvsBufferDescriptor newFrame = mCallbacks.get(i).onBufferRequested();
synchronized (mLock) {
if (newFrame != null) {
if (mBufferInUse[i] != null) {
bufferToReturn = mBufferInUse[i];
}
mBufferInUse[i] = newFrame;
}
bufferToRender = mBufferInUse[i];
}
if (bufferToRender == null) {
if (DBG) {
Log.d(TAG, "No buffer to draw from a callback " + i);
}
continue;
}
if (bufferToReturn != null) {
mCallbacks.get(i).onBufferProcessed(bufferToReturn);
}
// Specify a shader program to use
GLES20.glUseProgram(mProgram);
// Set a cameraMat as 4x4 identity matrix
int matrix = GLES20.glGetUniformLocation(mProgram, "cameraMat");
if (matrix < 0) {
throw new RuntimeException("Could not get a attribute location for cameraMat");
}
GLES20.glUniformMatrix4fv(/* location= */ matrix, /* count= */ 1,
/* transpose= */ false, /* value= */ sIdentityMatrix, 0);
// Retrieve a hardware buffer from a descriptor and update the texture
HardwareBuffer buffer = bufferToRender.getHardwareBuffer();
// Update the texture with a given hardware buffer
if (!nUpdateTexture(buffer, mTextureId[i])) {
throw new RuntimeException(
"Failed to update the texture with the preview frame");
}
}
// Select active texture unit
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// Render Hardware buffers
for (int i = 0; i < mTextureId.length; i++) {
mVertPos.put(mPositions[i]).position(0);
// Bind a named texture to the target
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId[i]);
// Use a texture slot 0 as the source
int sampler = GLES20.glGetUniformLocation(mProgram, "tex");
if (sampler < 0) {
throw new RuntimeException("Could not get a attribute location for tex");
}
GLES20.glUniform1i(sampler, 0);
// We'll ignore the alpha value
GLES20.glDisable(GLES20.GL_BLEND);
// Bind a named texture to the target
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId[i]);
// Define an array of generic vertex attribute data
GLES20.glVertexAttribPointer(0, 3, GLES20.GL_FLOAT, false, 0, mVertPos);
GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 0, mVertTex);
// Enable a generic vertex attribute array
GLES20.glEnableVertexAttribArray(0);
GLES20.glEnableVertexAttribArray(1);
// Render primitives from array data
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDisableVertexAttribArray(0);
GLES20.glDisableVertexAttribArray(1);
}
// Wait until all GL execution is complete
GLES20.glFinish();
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
// Use the GLES20 class's static methods instead of a passed GL10 interface.
GLES20.glViewport(0, 0, width, height);
mWidth = width;
mHeight = height;
}
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// Use the GLES20 class's static methods instead of a passed GL10 interface.
mProgram = buildShaderProgram(mVertexShader, mFragmentShader);
if (mProgram == 0) {
Log.e(TAG, "Failed to build shader programs");
return;
}
// Generate texture name
GLES20.glGenTextures(/* n= */ mTextureId.length, mTextureId, /* offset= */ 0);
for (var id : mTextureId) {
if (id <= 0) {
Log.e(TAG, "Did not get a texture handle, id=" + id);
continue;
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id);
// Use a linear interpolation to upscale the texture
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
// Use a nearest-neighbor to downscale the texture
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
// Clamp s, t coordinates at the edges
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
}
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader == 0) {
Log.e(TAG, "Failed to create a shader for " + source);
return 0;
}
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader " + shaderType + ": ");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
return 0;
}
return shader;
}
private int buildShaderProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
Log.e(TAG, "Failed to load a vertex shader");
return 0;
}
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (fragmentShader == 0) {
Log.e(TAG, "Failed to load a fragment shader");
return 0;
}
int program = GLES20.glCreateProgram();
if (program == 0) {
Log.e(TAG, "Failed to create a program");
return 0;
}
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, fragmentShader);
checkGlError("glAttachShader");
GLES20.glBindAttribLocation(program, 0, "pos");
GLES20.glBindAttribLocation(program, 1, "tex");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link a program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
return 0;
}
return program;
}
private static void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, op + ": glError " + error);
throw new RuntimeException(op + ": glError " + gluErrorString(error));
}
}
private native boolean nUpdateTexture(HardwareBuffer buffer, int textureId);
}