/* * 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 "GlWrapper.h" #include #include #include #include #include #include #include #include #include #include #include namespace { using ::aidl::android::frameworks::automotive::display::DisplayDesc; using ::aidl::android::frameworks::automotive::display::ICarDisplayProxy; using ::aidl::android::frameworks::automotive::display::Rotation; using ::aidl::android::hardware::common::NativeHandle; using ::aidl::android::hardware::graphics::common::HardwareBufferDescription; using ::android::GraphicBuffer; using ::android::sp; 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) { 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 = (char*)malloc(size); glGetShaderInfoLog(shader, size, nullptr, infoLog); LOG(ERROR) << " msg:" << std::endl << infoLog; free(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 aidl::android::hardware::automotive::evs::implementation { // Main entry point bool GlWrapper::initialize(const std::shared_ptr& pWindowProxy, uint64_t displayId) { LOG(DEBUG) << __FUNCTION__; if (!pWindowProxy) { LOG(ERROR) << "Could not get ICarDisplayProxy."; return false; } DisplayDesc displayDesc; auto status = pWindowProxy->getDisplayInfo(displayId, &displayDesc); if (!status.isOk()) { LOG(ERROR) << "Failed to read the display information"; return false; } mWidth = displayDesc.width; mHeight = displayDesc.height; if ((displayDesc.orientation != Rotation::ROTATION_0) && (displayDesc.orientation != Rotation::ROTATION_180)) { std::swap(mWidth, mHeight); } LOG(INFO) << "Display resolution is " << mWidth << "x" << mHeight; aidl::android::view::Surface shimSurface; status = pWindowProxy->getSurface(displayId, &shimSurface); if (!status.isOk()) { LOG(ERROR) << "Failed to obtain the surface."; return false; } mWindow = shimSurface.get(); if (mWindow == nullptr) { LOG(ERROR) << "Failed to get a native window from Surface."; return false; } ANativeWindow_acquire(mWindow); // 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[] = { // clang-format off // Tag Value EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_NONE // clang-format on }; // 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, " << getEGLError(); 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 if (eglGetCurrentContext() == mContext) { 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 if (mWindow == nullptr) { return; } ANativeWindow_release(mWindow); mWindow = nullptr; } void GlWrapper::showWindow(const std::shared_ptr& pWindowProxy, uint64_t id) { if (pWindowProxy) { pWindowProxy->showWindow(id); } else { LOG(ERROR) << "ICarDisplayProxy is not available."; } } void GlWrapper::hideWindow(const std::shared_ptr& pWindowProxy, uint64_t id) { if (pWindowProxy) { pWindowProxy->hideWindow(id); } else { LOG(ERROR) << "ICarDisplayProxy is not available."; } } bool GlWrapper::updateImageTexture(buffer_handle_t handle, const HardwareBufferDescription& description) { if (mKHRimage != EGL_NO_IMAGE_KHR) { return true; } // Create a temporary GraphicBuffer to wrap the provided handle. sp pGfxBuffer = new GraphicBuffer(description.width, description.height, static_cast<::android::PixelFormat>(description.format), description.layers, static_cast(description.usage), description.stride, const_cast(handle), /* keepOwnership= */ false); if (!pGfxBuffer) { 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(0.1f, 0.5f, 0.1f, 1.0f); 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[] = { // clang-format off -0.8, 0.8, 0.0f, // left top in window space 0.8, 0.8, 0.0f, // right top -0.8, -0.8, 0.0f, // left bottom 0.8, -0.8, 0.0f // right bottom // clang-format on }; // NOTE: We didn't flip the image in the texture, so V=0 is actually the top of the image GLfloat vertsCarTex[] = { // clang-format off 0.0f, 0.0f, // left top 1.0f, 0.0f, // right top 0.0f, 1.0f, // left bottom 1.0f, 1.0f // right bottom // clang-format on }; 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(); if (eglSwapBuffers(mDisplay, mSurface) == EGL_FALSE) { LOG(WARNING) << "Failed to swap EGL buffers, " << getEGLError(); } } } // namespace aidl::android::hardware::automotive::evs::implementation