blob: 9ad5729af8e9d954603045fb3194fce12f0a899a [file] [log] [blame]
/*
* Copyright (C) 2022 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 "GlWrapper.h"
#include <ui/DisplayMode.h>
#include <ui/DisplayState.h>
#include <ui/GraphicBuffer.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <utility>
using android::GraphicBuffer;
using android::sp;
namespace {
// Defines a default color to clear the screen in RGBA format
constexpr float kDefaultColorInRgba[] = {0.1f, 0.5f, 0.1f, 1.0f};
// Defines the size of the preview area relative to the entire display
constexpr float kDisplayAreaRatio = 0.8f;
constexpr const char vertexShaderSource[] =
"attribute vec4 pos; \n"
"attribute vec2 tex; \n"
"varying vec2 uv; \n"
"void main() \n"
"{ \n"
" gl_Position = pos; \n"
" uv = tex; \n"
"} \n";
constexpr const char pixelShaderSource[] =
"precision mediump float; \n"
"uniform sampler2D tex; \n"
"varying vec2 uv; \n"
"void main() \n"
"{ \n"
" gl_FragColor = texture2D(tex, uv); \n"
"} \n";
const char* getEGLError(void) {
switch (eglGetError()) {
case EGL_SUCCESS:
return "EGL_SUCCESS";
case EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS";
case EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC";
case EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE";
case EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT";
case EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG";
case EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE";
case EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY";
case EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE";
case EGL_BAD_MATCH:
return "EGL_BAD_MATCH";
case EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER";
case EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP";
case EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW";
case EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST";
default:
return "Unknown error";
}
}
// Given shader source, load and compile it
GLuint loadShader(GLenum type, const char* shaderSrc) {
// Create the shader object
GLuint shader = glCreateShader(type);
if (shader == 0) {
LOG(ERROR) << "glCreateSharder() failed with error = " << glGetError();
return 0;
}
// Load and compile the shader
glShaderSource(shader, 1, &shaderSrc, nullptr);
glCompileShader(shader);
// Verify the compilation worked as expected
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
LOG(ERROR) << "Error compiling shader";
GLint size = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size);
if (size > 0) {
// Get and report the error message
char infoLog[size];
glGetShaderInfoLog(shader, size, nullptr, infoLog);
LOG(ERROR) << " msg:" << std::endl << infoLog;
}
glDeleteShader(shader);
return 0;
}
return shader;
}
// Create a program object given vertex and pixels shader source
GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc) {
GLuint program = glCreateProgram();
if (program == 0) {
LOG(ERROR) << "Failed to allocate program object";
return 0;
}
// Compile the shaders and bind them to this program
GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vtxSrc);
if (vertexShader == 0) {
LOG(ERROR) << "Failed to load vertex shader";
glDeleteProgram(program);
return 0;
}
GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pxlSrc);
if (pixelShader == 0) {
LOG(ERROR) << "Failed to load pixel shader";
glDeleteProgram(program);
glDeleteShader(vertexShader);
return 0;
}
glAttachShader(program, vertexShader);
glAttachShader(program, pixelShader);
glBindAttribLocation(program, 0, "pos");
glBindAttribLocation(program, 1, "tex");
// Link the program
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
LOG(ERROR) << "Error linking program";
GLint size = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &size);
if (size > 0) {
// Get and report the error message
char* infoLog = (char*)malloc(size);
glGetProgramInfoLog(program, size, nullptr, infoLog);
LOG(ERROR) << " msg: " << infoLog;
free(infoLog);
}
glDeleteProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(pixelShader);
return 0;
}
return program;
}
} // namespace
namespace android::hardware::automotive::evs::V1_1::implementation {
// Main entry point
bool GlWrapper::initialize(const sp<IAutomotiveDisplayProxyService>& service, uint64_t displayId) {
LOG(DEBUG) << __FUNCTION__;
if (!service) {
LOG(WARNING) << "IAutomotiveDisplayProxyService is invalid.";
return false;
}
// We will use the first display in the list as the primary.
service->getDisplayInfo(displayId, [this](auto dpyConfig, auto dpyState) {
ui::DisplayMode* pConfig = reinterpret_cast<ui::DisplayMode*>(dpyConfig.data());
mWidth = pConfig->resolution.getWidth();
mHeight = pConfig->resolution.getHeight();
ui::DisplayState* pState = reinterpret_cast<ui::DisplayState*>(dpyState.data());
if (pState->orientation != ui::ROTATION_0 && pState->orientation != ui::ROTATION_180) {
// rotate
std::swap(mWidth, mHeight);
}
LOG(DEBUG) << "Display resolution is " << mWidth << " x " << mHeight;
});
mGfxBufferProducer = service->getIGraphicBufferProducer(displayId);
if (mGfxBufferProducer == nullptr) {
LOG(ERROR) << "Failed to get IGraphicBufferProducer from IAutomotiveDisplayProxyService.";
return false;
}
mSurfaceHolder = getSurfaceFromHGBP(mGfxBufferProducer);
if (mSurfaceHolder == nullptr) {
LOG(ERROR) << "Failed to get a Surface from HGBP.";
return false;
}
mWindow = getNativeWindow(mSurfaceHolder.get());
if (mWindow == nullptr) {
LOG(ERROR) << "Failed to get a native window from Surface.";
return false;
}
// Set up our OpenGL ES context associated with the default display
mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (mDisplay == EGL_NO_DISPLAY) {
LOG(ERROR) << "Failed to get egl display";
return false;
}
EGLint major = 2;
EGLint minor = 0;
if (!eglInitialize(mDisplay, &major, &minor)) {
LOG(ERROR) << "Failed to initialize EGL: " << getEGLError();
return false;
}
const EGLint config_attribs[] = {
// Tag Value
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_NONE};
// Pick the default configuration without constraints (is this good enough?)
EGLConfig egl_config = {0};
EGLint numConfigs = -1;
eglChooseConfig(mDisplay, config_attribs, &egl_config, 1, &numConfigs);
if (numConfigs != 1) {
LOG(ERROR) << "Didn't find a suitable format for our display window";
return false;
}
// Create the EGL render target surface
mSurface = eglCreateWindowSurface(mDisplay, egl_config, mWindow, nullptr);
if (mSurface == EGL_NO_SURFACE) {
LOG(ERROR) << "eglCreateWindowSurface failed: " << getEGLError();
;
return false;
}
// Create the EGL context
// NOTE: Our shader is (currently at least) written to require version 3, so this
// is required.
const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
mContext = eglCreateContext(mDisplay, egl_config, EGL_NO_CONTEXT, context_attribs);
if (mContext == EGL_NO_CONTEXT) {
LOG(ERROR) << "Failed to create OpenGL ES Context: " << getEGLError();
return false;
}
// Activate our render target for drawing
if (!eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) {
LOG(ERROR) << "Failed to make the OpenGL ES Context current: " << getEGLError();
return false;
}
// Create the shader program for our simple pipeline
mShaderProgram = buildShaderProgram(vertexShaderSource, pixelShaderSource);
if (!mShaderProgram) {
LOG(ERROR) << "Failed to build shader program: " << getEGLError();
return false;
}
// Create a GL texture that will eventually wrap our externally created texture surface(s)
glGenTextures(1, &mTextureMap);
if (mTextureMap <= 0) {
LOG(ERROR) << "Didn't get a texture handle allocated: " << getEGLError();
return false;
}
// Turn off mip-mapping for the created texture surface
// (the inbound camera imagery doesn't have MIPs)
glBindTexture(GL_TEXTURE_2D, mTextureMap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void GlWrapper::shutdown() {
// Drop our device textures
if (mKHRimage != EGL_NO_IMAGE_KHR) {
eglDestroyImageKHR(mDisplay, mKHRimage);
mKHRimage = EGL_NO_IMAGE_KHR;
}
// Release all GL resources
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(mDisplay, mSurface);
eglDestroyContext(mDisplay, mContext);
eglTerminate(mDisplay);
mSurface = EGL_NO_SURFACE;
mContext = EGL_NO_CONTEXT;
mDisplay = EGL_NO_DISPLAY;
// Release the window
mSurfaceHolder = nullptr;
}
void GlWrapper::showWindow(sp<IAutomotiveDisplayProxyService>& service, uint64_t id) {
if (service != nullptr) {
service->showWindow(id);
} else {
LOG(ERROR) << "IAutomotiveDisplayProxyService is not available.";
}
}
void GlWrapper::hideWindow(sp<IAutomotiveDisplayProxyService>& service, uint64_t id) {
if (service != nullptr) {
service->hideWindow(id);
} else {
LOG(ERROR) << "IAutomotiveDisplayProxyService is not available.";
}
}
bool GlWrapper::updateImageTexture(const V1_0::BufferDesc& buffer) {
BufferDesc newBuffer = {
.buffer =
{
.nativeHandle = buffer.memHandle,
},
.pixelSize = buffer.pixelSize,
.bufferId = buffer.bufferId,
};
AHardwareBuffer_Desc* pDesc =
reinterpret_cast<AHardwareBuffer_Desc*>(&newBuffer.buffer.description);
*pDesc = {
.width = buffer.width,
.height = buffer.height,
.layers = 1,
.format = buffer.format,
.usage = buffer.usage,
};
return updateImageTexture(newBuffer);
}
bool GlWrapper::updateImageTexture(const BufferDesc& aFrame) {
// If we haven't done it yet, create an "image" object to wrap the gralloc buffer
if (mKHRimage == EGL_NO_IMAGE_KHR) {
// create a temporary GraphicBuffer to wrap the provided handle
const AHardwareBuffer_Desc* pDesc =
reinterpret_cast<const AHardwareBuffer_Desc*>(&aFrame.buffer.description);
sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer(
pDesc->width, pDesc->height, pDesc->format, pDesc->layers, pDesc->usage,
pDesc->stride,
const_cast<native_handle_t*>(aFrame.buffer.nativeHandle.getNativeHandle()),
false /* keep ownership */
);
if (pGfxBuffer.get() == nullptr) {
LOG(ERROR) << "Failed to allocate GraphicBuffer to wrap our native handle";
return false;
}
// Get a GL compatible reference to the graphics buffer we've been given
EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
EGLClientBuffer cbuf = static_cast<EGLClientBuffer>(pGfxBuffer->getNativeBuffer());
mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, cbuf,
eglImageAttributes);
if (mKHRimage == EGL_NO_IMAGE_KHR) {
LOG(ERROR) << "Error creating EGLImage: " << getEGLError();
return false;
}
// Update the texture handle we already created to refer to this gralloc buffer
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTextureMap);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, static_cast<GLeglImageOES>(mKHRimage));
}
return true;
}
void GlWrapper::renderImageToScreen() {
// Set the viewport
glViewport(0, 0, mWidth, mHeight);
// Clear the color buffer
glClearColor(kDefaultColorInRgba[0], kDefaultColorInRgba[1],
kDefaultColorInRgba[2], kDefaultColorInRgba[3]);
glClear(GL_COLOR_BUFFER_BIT);
// Select our screen space simple texture shader
glUseProgram(mShaderProgram);
// Bind the texture and assign it to the shader's sampler
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTextureMap);
GLint sampler = glGetUniformLocation(mShaderProgram, "tex");
glUniform1i(sampler, 0);
// We want our image to show up opaque regardless of alpha values
glDisable(GL_BLEND);
// Draw a rectangle on the screen
GLfloat vertsCarPos[] = {
-kDisplayAreaRatio, kDisplayAreaRatio, 0.0f, // left top in window space
kDisplayAreaRatio, kDisplayAreaRatio, 0.0f, // right top
-kDisplayAreaRatio, -kDisplayAreaRatio, 0.0f, // left bottom
kDisplayAreaRatio, -kDisplayAreaRatio, 0.0f // right bottom
};
// NOTE: We didn't flip the image in the texture, so V=0 is actually the top of the image
GLfloat vertsCarTex[] = {
0.0f, 0.0f, // left top
1.0f, 0.0f, // right top
0.0f, 1.0f, // left bottom
1.0f, 1.0f // right bottom
};
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertsCarPos);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, vertsCarTex);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Clean up and flip the rendered result to the front so it is visible
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glFinish();
eglSwapBuffers(mDisplay, mSurface);
}
} // namespace android::hardware::automotive::evs::V1_1::implementation