/* * 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 #include #include #include #include #include #include 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& 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(dpyConfig.data()); mWidth = pConfig->resolution.getWidth(); mHeight = pConfig->resolution.getHeight(); ui::DisplayState* pState = reinterpret_cast(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& service, uint64_t id) { if (service != nullptr) { service->showWindow(id); } else { LOG(ERROR) << "IAutomotiveDisplayProxyService is not available."; } } void GlWrapper::hideWindow(sp& 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(&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(&aFrame.buffer.description); sp pGfxBuffer = new GraphicBuffer( pDesc->width, pDesc->height, pDesc->format, pDesc->layers, pDesc->usage, pDesc->stride, const_cast(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(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(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