Update a default HIDL EVS HAL implementation
This CL modifies a default implementation of HIDL EVS HAL v1.1 to
properly emulate IEvsCamera and generate a test pattern (SMPTE color
bars) on the cuttlefish.
Bug: 147743625
Test: launch_cvd --gpu_mode=gfxstream && atest VtsHalEvsV1_1TargetTest
Change-Id: I36b141c250efcc27e9a455d504fe897c69349ad9
diff --git a/automotive/evs/1.1/default/GlWrapper.cpp b/automotive/evs/1.1/default/GlWrapper.cpp
new file mode 100644
index 0000000..357b67c
--- /dev/null
+++ b/automotive/evs/1.1/default/GlWrapper.cpp
@@ -0,0 +1,450 @@
+/*
+ * 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[] =
+ ""
+ "#version 300 es \n"
+ "layout(location = 0) in vec4 pos; \n"
+ "layout(location = 1) in vec2 tex; \n"
+ "out vec2 uv; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_Position = pos; \n"
+ " uv = tex; \n"
+ "} \n";
+
+constexpr const char pixelShaderSource[] =
+ "#version 300 es \n"
+ "precision mediump float; \n"
+ "uniform sampler2D tex; \n"
+ "in vec2 uv; \n"
+ "out vec4 color; \n"
+ "void main() \n"
+ "{ \n"
+ " vec4 texel = texture(tex, uv); \n"
+ " color = texel; \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);
+
+ // 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 = 3;
+ 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