| /* |
| * Copyright (C) 2017 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 <stdio.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| |
| #include <ui/DisplayInfo.h> |
| #include <ui/GraphicBuffer.h> |
| |
| |
| using namespace android; |
| |
| |
| using android::GraphicBuffer; |
| using android::sp; |
| |
| |
| 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"; |
| |
| 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"; |
| |
| |
| static 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 |
| static 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) { |
| ALOGE("Error compiling shader\n"); |
| |
| 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); |
| ALOGE(" msg:\n%s\n", infoLog); |
| free(infoLog); |
| } |
| |
| glDeleteShader(shader); |
| return 0; |
| } |
| |
| return shader; |
| } |
| |
| |
| // Create a program object given vertex and pixels shader source |
| static GLuint buildShaderProgram(const char* vtxSrc, const char* pxlSrc) { |
| GLuint program = glCreateProgram(); |
| if (program == 0) { |
| ALOGE("Failed to allocate program object\n"); |
| return 0; |
| } |
| |
| // Compile the shaders and bind them to this program |
| GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vtxSrc); |
| if (vertexShader == 0) { |
| ALOGE("Failed to load vertex shader\n"); |
| glDeleteProgram(program); |
| return 0; |
| } |
| GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pxlSrc); |
| if (pixelShader == 0) { |
| ALOGE("Failed to load pixel shader\n"); |
| 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) |
| { |
| ALOGE("Error linking program.\n"); |
| 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); |
| ALOGE(" msg: %s\n", infoLog); |
| free(infoLog); |
| } |
| |
| glDeleteProgram(program); |
| glDeleteShader(vertexShader); |
| glDeleteShader(pixelShader); |
| return 0; |
| } |
| |
| return program; |
| } |
| |
| |
| // Main entry point |
| bool GlWrapper::initialize() { |
| // |
| // Create the native full screen window and get a suitable configuration to match it |
| // |
| status_t err; |
| |
| mFlinger = new SurfaceComposerClient(); |
| if (mFlinger == nullptr) { |
| ALOGE("SurfaceComposerClient couldn't be allocated"); |
| return false; |
| } |
| err = mFlinger->initCheck(); |
| if (err != NO_ERROR) { |
| ALOGE("SurfaceComposerClient::initCheck error: %#x", err); |
| return false; |
| } |
| |
| // Get main display parameters. |
| sp<IBinder> mainDpy = SurfaceComposerClient::getInternalDisplayToken(); |
| if (mainDpy == nullptr) { |
| ALOGE("ERROR: no internal display"); |
| return false; |
| } |
| |
| DisplayInfo mainDpyInfo; |
| err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo); |
| if (err != NO_ERROR) { |
| ALOGE("ERROR: unable to get display characteristics"); |
| return false; |
| } |
| |
| if (mainDpyInfo.orientation != DISPLAY_ORIENTATION_0 && |
| mainDpyInfo.orientation != DISPLAY_ORIENTATION_180) { |
| // rotated |
| mWidth = mainDpyInfo.h; |
| mHeight = mainDpyInfo.w; |
| } else { |
| mWidth = mainDpyInfo.w; |
| mHeight = mainDpyInfo.h; |
| } |
| |
| mFlingerSurfaceControl = mFlinger->createSurface( |
| String8("Evs Display"), mWidth, mHeight, |
| PIXEL_FORMAT_RGBX_8888, ISurfaceComposerClient::eOpaque); |
| if (mFlingerSurfaceControl == nullptr || !mFlingerSurfaceControl->isValid()) { |
| ALOGE("Failed to create SurfaceControl"); |
| return false; |
| } |
| mFlingerSurface = mFlingerSurfaceControl->getSurface(); |
| |
| |
| // Set up our OpenGL ES context associated with the default display |
| mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
| if (mDisplay == EGL_NO_DISPLAY) { |
| ALOGE("Failed to get egl display"); |
| return false; |
| } |
| |
| EGLint major = 3; |
| EGLint minor = 0; |
| if (!eglInitialize(mDisplay, &major, &minor)) { |
| ALOGE("Failed to initialize EGL: %s", 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) { |
| ALOGE("Didn't find a suitable format for our display window"); |
| return false; |
| } |
| |
| // Create the EGL render target surface |
| mSurface = eglCreateWindowSurface(mDisplay, egl_config, mFlingerSurface.get(), nullptr); |
| if (mSurface == EGL_NO_SURFACE) { |
| ALOGE("gelCreateWindowSurface failed."); |
| 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) { |
| ALOGE("Failed to create OpenGL ES Context: %s", getEGLError()); |
| return false; |
| } |
| |
| |
| // Activate our render target for drawing |
| if (!eglMakeCurrent(mDisplay, mSurface, mSurface, mContext)) { |
| ALOGE("Failed to make the OpenGL ES Context current: %s", getEGLError()); |
| return false; |
| } |
| |
| |
| // Create the shader program for our simple pipeline |
| mShaderProgram = buildShaderProgram(vertexShaderSource, pixelShaderSource); |
| if (!mShaderProgram) { |
| ALOGE("Failed to build shader program: %s", getEGLError()); |
| return false; |
| } |
| |
| // Create a GL texture that will eventually wrap our externally created texture surface(s) |
| glGenTextures(1, &mTextureMap); |
| if (mTextureMap <= 0) { |
| ALOGE("Didn't get a texture handle allocated: %s", 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; |
| |
| // Let go of our SurfaceComposer resources |
| mFlingerSurface.clear(); |
| mFlingerSurfaceControl.clear(); |
| mFlinger.clear(); |
| } |
| |
| |
| void GlWrapper::showWindow() { |
| if (mFlingerSurfaceControl != nullptr) { |
| SurfaceComposerClient::Transaction{} |
| .setLayer(mFlingerSurfaceControl, 0x7FFFFFFF) // always on top |
| .show(mFlingerSurfaceControl) |
| .apply(); |
| } |
| } |
| |
| |
| void GlWrapper::hideWindow() { |
| if (mFlingerSurfaceControl != nullptr) { |
| SurfaceComposerClient::Transaction{} |
| .hide(mFlingerSurfaceControl) |
| .apply(); |
| } |
| } |
| |
| |
| bool GlWrapper::updateImageTexture(const BufferDesc& buffer) { |
| |
| // 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 |
| sp<GraphicBuffer> pGfxBuffer = new GraphicBuffer( |
| buffer.width, |
| buffer.height, |
| buffer.format, |
| 1, /* layer count */ |
| buffer.usage, |
| buffer.stride, |
| const_cast<native_handle_t*>(buffer.memHandle.getNativeHandle()), |
| false /* keep ownership */ |
| ); |
| if (pGfxBuffer.get() == nullptr) { |
| ALOGE("Failed to allocate GraphicsBuffer 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()); |
| // TODO: If we pass in a context, we get "bad context" back |
| #if 0 |
| mKHRimage = eglCreateImageKHR(mDisplay, mContext, |
| EGL_NATIVE_BUFFER_ANDROID, cbuf, |
| eglImageAttributes); |
| #else |
| mKHRimage = eglCreateImageKHR(mDisplay, EGL_NO_CONTEXT, |
| EGL_NATIVE_BUFFER_ANDROID, cbuf, |
| eglImageAttributes); |
| #endif |
| if (mKHRimage == EGL_NO_IMAGE_KHR) { |
| ALOGE("error creating EGLImage: %s", 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(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[] = { -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 |
| }; |
| |
| // 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); |
| } |
| |