| /* |
| * Copyright (C) 2015 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. |
| */ |
| |
| #define ATRACE_TAG ATRACE_TAG_GRAPHICS |
| #define LOG_TAG "GLWorker" |
| |
| #include <string> |
| #include <sstream> |
| |
| #include <sys/resource.h> |
| |
| #include <sync/sync.h> |
| #include <sw_sync.h> |
| |
| #include <hardware/hardware.h> |
| #include <hardware/hwcomposer.h> |
| |
| #include <ui/GraphicBuffer.h> |
| #include <ui/PixelFormat.h> |
| |
| #include <utils/Trace.h> |
| |
| #include "glworker.h" |
| |
| #include "seperate_rects.h" |
| |
| // TODO(zachr): use hwc_drm_bo to turn buffer handles into textures |
| #ifndef EGL_NATIVE_HANDLE_ANDROID_NVX |
| #define EGL_NATIVE_HANDLE_ANDROID_NVX 0x322A |
| #endif |
| |
| #define MAX_OVERLAPPING_LAYERS 64 |
| |
| namespace android { |
| |
| typedef seperate_rects::Rect<float> FRect; |
| typedef seperate_rects::RectSet<uint64_t, float> FRectSet; |
| |
| static const char *GetGLError(void) { |
| switch (glGetError()) { |
| case GL_NO_ERROR: |
| return "GL_NO_ERROR"; |
| case GL_INVALID_ENUM: |
| return "GL_INVALID_ENUM"; |
| case GL_INVALID_VALUE: |
| return "GL_INVALID_VALUE"; |
| case GL_INVALID_OPERATION: |
| return "GL_INVALID_OPERATION"; |
| case GL_INVALID_FRAMEBUFFER_OPERATION: |
| return "GL_INVALID_FRAMEBUFFER_OPERATION"; |
| case GL_OUT_OF_MEMORY: |
| return "GL_OUT_OF_MEMORY"; |
| default: |
| return "Unknown error"; |
| } |
| } |
| |
| static const char *GetGLFramebufferError(void) { |
| switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) { |
| case GL_FRAMEBUFFER_COMPLETE: |
| return "GL_FRAMEBUFFER_COMPLETE"; |
| case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: |
| return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; |
| case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: |
| return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; |
| case GL_FRAMEBUFFER_UNSUPPORTED: |
| return "GL_FRAMEBUFFER_UNSUPPORTED"; |
| case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: |
| return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; |
| default: |
| return "Unknown error"; |
| } |
| } |
| |
| 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"; |
| } |
| } |
| |
| static bool HasExtension(const char *extension, const char *extensions) { |
| const char *start, *where, *terminator; |
| start = extensions; |
| for (;;) { |
| where = (char *)strstr((const char *)start, extension); |
| if (!where) |
| break; |
| terminator = where + strlen(extension); |
| if (where == start || *(where - 1) == ' ') |
| if (*terminator == ' ' || *terminator == '\0') |
| return true; |
| start = terminator; |
| } |
| return false; |
| } |
| |
| static AutoGLShader CompileAndCheckShader(GLenum type, unsigned source_count, |
| const GLchar **sources, |
| std::string *shader_log) { |
| GLint status; |
| AutoGLShader shader(glCreateShader(type)); |
| if (shader.get() == 0) { |
| *shader_log = "glCreateShader failed"; |
| return 0; |
| } |
| glShaderSource(shader.get(), source_count, sources, NULL); |
| glCompileShader(shader.get()); |
| glGetShaderiv(shader.get(), GL_COMPILE_STATUS, &status); |
| if (!status) { |
| if (shader_log) { |
| GLint log_length; |
| glGetShaderiv(shader.get(), GL_INFO_LOG_LENGTH, &log_length); |
| shader_log->resize(log_length); |
| glGetShaderInfoLog(shader.get(), log_length, NULL, &(*shader_log)[0]); |
| } |
| return 0; |
| } |
| |
| return shader; |
| } |
| |
| static int GenerateShaders(std::vector<AutoGLProgram> *blend_programs) { |
| // Limits: GL_MAX_VARYING_COMPONENTS, GL_MAX_TEXTURE_IMAGE_UNITS, |
| // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS |
| // clang-format off |
| const GLchar *shader_preamble = "#version 300 es\n#define LAYER_COUNT "; |
| |
| const GLchar *vertex_shader_source = |
| "\n" |
| "precision mediump int; \n" |
| "uniform vec4 uViewport; \n" |
| "uniform sampler2D uLayerTextures[LAYER_COUNT]; \n" |
| "uniform vec4 uLayerCrop[LAYER_COUNT]; \n" |
| "in vec2 vPosition; \n" |
| "in vec2 vTexCoords; \n" |
| "out vec2 fTexCoords[LAYER_COUNT]; \n" |
| "void main() { \n" |
| " for (int i = 0; i < LAYER_COUNT; i++) { \n" |
| " fTexCoords[i] = (uLayerCrop[i].xy + vTexCoords * uLayerCrop[i].zw) / \n" |
| " vec2(textureSize(uLayerTextures[i], 0)); \n" |
| " } \n" |
| " vec2 scaledPosition = uViewport.xy + vPosition * uViewport.zw; \n" |
| " gl_Position = vec4(scaledPosition * vec2(2.0) - vec2(1.0), 0.0, 1.0); \n" |
| "} \n"; |
| |
| const GLchar *fragment_shader_source = |
| "\n" |
| "precision mediump float; \n" |
| "uniform sampler2D uLayerTextures[LAYER_COUNT]; \n" |
| "uniform float uLayerAlpha[LAYER_COUNT]; \n" |
| "in vec2 fTexCoords[LAYER_COUNT]; \n" |
| "out vec4 oFragColor; \n" |
| "void main() { \n" |
| " vec3 color = vec3(0.0, 0.0, 0.0); \n" |
| " float alphaCover = 1.0; \n" |
| " for (int i = 0; i < LAYER_COUNT; i++) { \n" |
| " vec4 texSample = texture(uLayerTextures[i], fTexCoords[i]); \n" |
| " float a = texSample.a * uLayerAlpha[i]; \n" |
| " color += a * alphaCover * texSample.rgb; \n" |
| " alphaCover *= 1.0 - a; \n" |
| " if (alphaCover <= 0.5/255.0) \n" |
| " break; \n" |
| " } \n" |
| " oFragColor = vec4(color, 1.0 - alphaCover); \n" |
| "} \n"; |
| // clang-format on |
| |
| int i, ret = 1; |
| GLint max_texture_images, status; |
| AutoGLShader vertex_shader, fragment_shader; |
| AutoGLProgram program; |
| std::string shader_log; |
| |
| glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_texture_images); |
| |
| for (i = 1; i <= max_texture_images; i++) { |
| std::ostringstream layer_count_formatter; |
| layer_count_formatter << i; |
| std::string layer_count(layer_count_formatter.str()); |
| const GLchar *shader_sources[3] = {shader_preamble, layer_count.c_str(), |
| NULL}; |
| |
| shader_sources[2] = vertex_shader_source; |
| vertex_shader = CompileAndCheckShader(GL_VERTEX_SHADER, 3, shader_sources, |
| ret ? &shader_log : NULL); |
| if (!vertex_shader.get()) { |
| if (ret) |
| ALOGE("Failed to make vertex shader:\n%s", shader_log.c_str()); |
| break; |
| } |
| |
| shader_sources[2] = fragment_shader_source; |
| fragment_shader = CompileAndCheckShader( |
| GL_FRAGMENT_SHADER, 3, shader_sources, ret ? &shader_log : NULL); |
| if (!fragment_shader.get()) { |
| if (ret) |
| ALOGE("Failed to make fragment shader:\n%s", shader_log.c_str()); |
| break; |
| } |
| |
| program = AutoGLProgram(glCreateProgram()); |
| if (!program.get()) { |
| if (ret) |
| ALOGE("Failed to create program %s", GetGLError()); |
| break; |
| } |
| |
| glAttachShader(program.get(), vertex_shader.get()); |
| glAttachShader(program.get(), fragment_shader.get()); |
| glBindAttribLocation(program.get(), 0, "vPosition"); |
| glBindAttribLocation(program.get(), 1, "vTexCoords"); |
| glLinkProgram(program.get()); |
| glDetachShader(program.get(), vertex_shader.get()); |
| glDetachShader(program.get(), fragment_shader.get()); |
| |
| glGetProgramiv(program.get(), GL_LINK_STATUS, &status); |
| if (!status) { |
| if (ret) { |
| GLint log_length; |
| glGetProgramiv(program.get(), GL_INFO_LOG_LENGTH, &log_length); |
| std::string program_log(log_length, ' '); |
| glGetProgramInfoLog(program.get(), log_length, NULL, &program_log[0]); |
| ALOGE("Failed to link program: \n%s", program_log.c_str()); |
| } |
| break; |
| } |
| |
| ret = 0; |
| blend_programs->emplace_back(std::move(program)); |
| } |
| |
| return ret; |
| } |
| |
| struct RenderingCommand { |
| struct TextureSource { |
| unsigned texture_index; |
| float crop_bounds[4]; |
| float alpha; |
| }; |
| |
| float bounds[4]; |
| unsigned texture_count; |
| TextureSource textures[MAX_OVERLAPPING_LAYERS]; |
| |
| RenderingCommand() : texture_count(0) { |
| } |
| }; |
| |
| static void ConstructCommands(const hwc_layer_1 *layers, size_t num_layers, |
| std::vector<RenderingCommand> *commands) { |
| std::vector<FRect> in_rects; |
| std::vector<FRectSet> out_rects; |
| int i; |
| |
| for (unsigned rect_index = 0; rect_index < num_layers; rect_index++) { |
| const hwc_layer_1 &layer = layers[rect_index]; |
| FRect rect; |
| in_rects.push_back(FRect(layer.displayFrame.left, layer.displayFrame.top, |
| layer.displayFrame.right, |
| layer.displayFrame.bottom)); |
| } |
| |
| seperate_frects_64(in_rects, &out_rects); |
| |
| for (unsigned rect_index = 0; rect_index < out_rects.size(); rect_index++) { |
| const FRectSet &out_rect = out_rects[rect_index]; |
| commands->push_back(RenderingCommand()); |
| RenderingCommand &cmd = commands->back(); |
| |
| memcpy(cmd.bounds, out_rect.rect.bounds, sizeof(cmd.bounds)); |
| |
| uint64_t tex_set = out_rect.id_set.getBits(); |
| for (unsigned i = num_layers - 1; tex_set != 0x0; i--) { |
| if (tex_set & (0x1 << i)) { |
| tex_set &= ~(0x1 << i); |
| |
| const hwc_layer_1 &layer = layers[i]; |
| |
| FRect display_rect(layer.displayFrame.left, layer.displayFrame.top, |
| layer.displayFrame.right, layer.displayFrame.bottom); |
| float display_size[2] = { |
| display_rect.bounds[2] - display_rect.bounds[0], |
| display_rect.bounds[3] - display_rect.bounds[1]}; |
| |
| FRect crop_rect(layer.sourceCropf.left, layer.sourceCropf.top, |
| layer.sourceCropf.right, layer.sourceCropf.bottom); |
| float crop_size[2] = {crop_rect.bounds[2] - crop_rect.bounds[0], |
| crop_rect.bounds[3] - crop_rect.bounds[1]}; |
| |
| RenderingCommand::TextureSource &src = cmd.textures[cmd.texture_count]; |
| cmd.texture_count++; |
| src.texture_index = i; |
| |
| for (int b = 0; b < 4; b++) { |
| float bound_percent = (cmd.bounds[b] - display_rect.bounds[b % 2]) / |
| display_size[b % 2]; |
| src.crop_bounds[b] = |
| crop_rect.bounds[b % 2] + bound_percent * crop_size[b % 2]; |
| } |
| |
| if (layer.blending == HWC_BLENDING_NONE) { |
| src.alpha = 1.0f; |
| // This layer is opaque. There is no point in using layers below this |
| // one. |
| break; |
| } |
| |
| src.alpha = layer.planeAlpha / 255.0f; |
| } |
| } |
| } |
| } |
| |
| static int EGLFenceWait(EGLDisplay egl_display, int acquireFenceFd) { |
| int ret = 0; |
| |
| EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, acquireFenceFd, |
| EGL_NONE}; |
| EGLSyncKHR egl_sync = |
| eglCreateSyncKHR(egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); |
| if (egl_sync == EGL_NO_SYNC_KHR) { |
| ALOGE("Failed to make EGLSyncKHR from acquireFenceFd: %s", GetEGLError()); |
| close(acquireFenceFd); |
| return 1; |
| } |
| |
| EGLint success = eglWaitSyncKHR(egl_display, egl_sync, 0); |
| if (success == EGL_FALSE) { |
| ALOGE("Failed to wait for acquire: %s", GetEGLError()); |
| ret = 1; |
| } |
| eglDestroySyncKHR(egl_display, egl_sync); |
| |
| return ret; |
| } |
| |
| static int CreateTextureFromHandle(EGLDisplay egl_display, |
| buffer_handle_t handle, |
| AutoEGLImageAndGLTexture *out) { |
| EGLImageKHR image = eglCreateImageKHR( |
| egl_display, EGL_NO_CONTEXT, EGL_NATIVE_HANDLE_ANDROID_NVX, |
| (EGLClientBuffer)handle, NULL /* no attribs */); |
| |
| if (image == EGL_NO_IMAGE_KHR) { |
| ALOGE("Failed to make image %s %p", GetEGLError(), handle); |
| return -EINVAL; |
| } |
| |
| GLuint texture; |
| glGenTextures(1, &texture); |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| |
| out->image.reset(image); |
| out->texture.reset(texture); |
| |
| return 0; |
| } |
| |
| GLWorker::Compositor::Compositor() |
| : egl_display_(EGL_NO_DISPLAY), egl_ctx_(EGL_NO_CONTEXT) { |
| } |
| |
| int GLWorker::Compositor::Init() { |
| int ret = 0; |
| const char *egl_extensions; |
| const char *gl_extensions; |
| EGLint num_configs; |
| EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE}; |
| EGLConfig egl_config; |
| |
| // clang-format off |
| const GLfloat verts[] = { |
| 0.0f, 0.0f, 0.0f, 0.0f, |
| 0.0f, 2.0f, 0.0f, 2.0f, |
| 2.0f, 0.0f, 2.0f, 0.0f |
| }; |
| // clang-format on |
| |
| const EGLint config_attribs[] = {EGL_RENDERABLE_TYPE, |
| EGL_OPENGL_ES2_BIT, |
| EGL_RED_SIZE, |
| 8, |
| EGL_GREEN_SIZE, |
| 8, |
| EGL_BLUE_SIZE, |
| 8, |
| EGL_NONE}; |
| |
| const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; |
| |
| egl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
| if (egl_display_ == EGL_NO_DISPLAY) { |
| ALOGE("Failed to get egl display"); |
| return 1; |
| } |
| |
| if (!eglInitialize(egl_display_, NULL, NULL)) { |
| ALOGE("Failed to initialize egl: %s", GetEGLError()); |
| return 1; |
| } |
| |
| egl_extensions = eglQueryString(egl_display_, EGL_EXTENSIONS); |
| |
| // These extensions are all technically required but not always reported due |
| // to meta EGL filtering them out. |
| if (!HasExtension("EGL_KHR_image_base", egl_extensions)) |
| ALOGW("EGL_KHR_image_base extension not supported"); |
| |
| if (!HasExtension("EGL_ANDROID_image_native_buffer", egl_extensions)) |
| ALOGW("EGL_ANDROID_image_native_buffer extension not supported"); |
| |
| if (!HasExtension("EGL_ANDROID_native_fence_sync", egl_extensions)) |
| ALOGW("EGL_ANDROID_native_fence_sync extension not supported"); |
| |
| if (!eglChooseConfig(egl_display_, config_attribs, &egl_config, 1, |
| &num_configs)) { |
| ALOGE("eglChooseConfig() failed with error: %s", GetEGLError()); |
| return 1; |
| } |
| |
| egl_ctx_ = |
| eglCreateContext(egl_display_, egl_config, |
| EGL_NO_CONTEXT /* No shared context */, context_attribs); |
| |
| if (egl_ctx_ == EGL_NO_CONTEXT) { |
| ALOGE("Failed to create OpenGL ES Context: %s", GetEGLError()); |
| return 1; |
| } |
| |
| if (!eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_ctx_)) { |
| ALOGE("Failed to make the OpenGL ES Context current: %s", GetEGLError()); |
| return 1; |
| } |
| |
| gl_extensions = (const char *)glGetString(GL_EXTENSIONS); |
| |
| if (!HasExtension("GL_OES_EGL_image", gl_extensions)) |
| ALOGW("GL_OES_EGL_image extension not supported"); |
| |
| GLuint vertex_buffer; |
| glGenBuffers(1, &vertex_buffer); |
| glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); |
| glBindBuffer(GL_ARRAY_BUFFER, 0); |
| vertex_buffer_.reset(vertex_buffer); |
| |
| if (GenerateShaders(&blend_programs_)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| GLWorker::Compositor::~Compositor() { |
| if (egl_display_ != EGL_NO_DISPLAY && egl_ctx_ != EGL_NO_CONTEXT) |
| if (eglDestroyContext(egl_display_, egl_ctx_) == EGL_FALSE) |
| ALOGE("Failed to destroy OpenGL ES Context: %s", GetEGLError()); |
| } |
| |
| int GLWorker::Compositor::Composite(hwc_layer_1 *layers, size_t num_layers, |
| sp<GraphicBuffer> framebuffer) { |
| ATRACE_CALL(); |
| int ret = 0; |
| size_t i; |
| std::vector<AutoEGLImageAndGLTexture> layer_textures; |
| std::vector<RenderingCommand> commands; |
| |
| if (num_layers == 0) { |
| return -EALREADY; |
| } |
| |
| GLint frame_width = framebuffer->getWidth(); |
| GLint frame_height = framebuffer->getHeight(); |
| EGLSyncKHR finished_sync; |
| |
| AutoEGLImageKHR egl_fb_image( |
| eglCreateImageKHR(egl_display_, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, |
| (EGLClientBuffer)framebuffer->getNativeBuffer(), |
| NULL /* no attribs */), |
| EGLImageDeleter(egl_display_)); |
| |
| if (egl_fb_image.get() == EGL_NO_IMAGE_KHR) { |
| ALOGE("Failed to make image from target buffer: %s", GetEGLError()); |
| return -EINVAL; |
| } |
| |
| GLuint gl_fb_tex; |
| glGenTextures(1, &gl_fb_tex); |
| AutoGLTexture gl_fb_tex_auto(gl_fb_tex); |
| glBindTexture(GL_TEXTURE_2D, gl_fb_tex); |
| glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, |
| (GLeglImageOES)egl_fb_image.get()); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| |
| GLuint gl_fb; |
| glGenFramebuffers(1, &gl_fb); |
| AutoGLFramebuffer gl_fb_auto(gl_fb); |
| glBindFramebuffer(GL_FRAMEBUFFER, gl_fb); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| gl_fb_tex, 0); |
| |
| if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { |
| ALOGE("Failed framebuffer check for created target buffer: %s", |
| GetGLFramebufferError()); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < num_layers; i++) { |
| const struct hwc_layer_1 *layer = &layers[i]; |
| |
| if (ret) { |
| if (layer->acquireFenceFd >= 0) |
| close(layer->acquireFenceFd); |
| continue; |
| } |
| |
| layer_textures.emplace_back(egl_display_); |
| ret = CreateTextureFromHandle(egl_display_, layer->handle, |
| &layer_textures.back()); |
| if (!ret) { |
| ret = EGLFenceWait(egl_display_, layer->acquireFenceFd); |
| } |
| if (ret) { |
| layer_textures.pop_back(); |
| ret = -EINVAL; |
| } |
| } |
| |
| if (ret) |
| return ret; |
| |
| ConstructCommands(layers, num_layers, &commands); |
| |
| glViewport(0, 0, frame_width, frame_height); |
| |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_.get()); |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, NULL); |
| glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, |
| (void *)(sizeof(float) * 2)); |
| glEnableVertexAttribArray(0); |
| glEnableVertexAttribArray(1); |
| glEnable(GL_SCISSOR_TEST); |
| |
| for (const RenderingCommand &cmd : commands) { |
| if (cmd.texture_count <= 0) { |
| continue; |
| } |
| |
| // TODO(zachr): handle the case of too many overlapping textures for one |
| // area by falling back to rendering as many layers as possible using |
| // multiple blending passes. |
| if (cmd.texture_count > blend_programs_.size()) { |
| ALOGE("Too many layers to render in one area"); |
| continue; |
| } |
| |
| GLint program = blend_programs_[cmd.texture_count - 1].get(); |
| glUseProgram(program); |
| GLint gl_viewport_loc = glGetUniformLocation(program, "uViewport"); |
| GLint gl_tex_loc = glGetUniformLocation(program, "uLayerTextures"); |
| GLint gl_crop_loc = glGetUniformLocation(program, "uLayerCrop"); |
| GLint gl_alpha_loc = glGetUniformLocation(program, "uLayerAlpha"); |
| glUniform4f(gl_viewport_loc, cmd.bounds[0] / (float)frame_width, |
| cmd.bounds[1] / (float)frame_height, |
| (cmd.bounds[2] - cmd.bounds[0]) / (float)frame_width, |
| (cmd.bounds[3] - cmd.bounds[1]) / (float)frame_height); |
| |
| for (unsigned src_index = 0; src_index < cmd.texture_count; src_index++) { |
| const RenderingCommand::TextureSource &src = cmd.textures[src_index]; |
| glUniform1f(gl_alpha_loc + src_index, src.alpha); |
| glUniform4f(gl_crop_loc + src_index, src.crop_bounds[0], |
| src.crop_bounds[1], src.crop_bounds[2] - src.crop_bounds[0], |
| src.crop_bounds[3] - src.crop_bounds[1]); |
| |
| glUniform1i(gl_tex_loc + src_index, src_index); |
| glActiveTexture(GL_TEXTURE0 + src_index); |
| glBindTexture(GL_TEXTURE_2D, |
| layer_textures[src.texture_index].texture.get()); |
| } |
| |
| glScissor(cmd.bounds[0], cmd.bounds[1], cmd.bounds[2] - cmd.bounds[0], |
| cmd.bounds[3] - cmd.bounds[1]); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| for (unsigned src_index = 0; src_index < cmd.texture_count; src_index++) { |
| glActiveTexture(GL_TEXTURE0 + src_index); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| } |
| } |
| |
| glDisable(GL_SCISSOR_TEST); |
| glActiveTexture(GL_TEXTURE0); |
| glDisableVertexAttribArray(0); |
| glDisableVertexAttribArray(1); |
| glBindBuffer(GL_ARRAY_BUFFER, 0); |
| glUseProgram(0); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| |
| return ret; |
| } |
| |
| int GLWorker::DoComposition(Compositor &compositor, Work *work) { |
| int ret = |
| compositor.Composite(work->layers, work->num_layers, work->framebuffer); |
| |
| int timeline_fd = work->timeline_fd; |
| work->timeline_fd = -1; |
| |
| if (ret) { |
| worker_ret_ = ret; |
| glFinish(); |
| sw_sync_timeline_inc(timeline_fd, work->num_layers); |
| close(timeline_fd); |
| return pthread_cond_signal(&work_done_cond_); |
| } |
| |
| unsigned timeline_count = work->num_layers + 1; |
| worker_ret_ = sw_sync_fence_create(timeline_fd, "GLComposition done fence", |
| timeline_count); |
| ret = pthread_cond_signal(&work_done_cond_); |
| |
| glFinish(); |
| |
| sw_sync_timeline_inc(timeline_fd, timeline_count); |
| close(timeline_fd); |
| |
| return ret; |
| } |
| |
| GLWorker::GLWorker() : initialized_(false) { |
| } |
| |
| GLWorker::~GLWorker() { |
| if (!initialized_) |
| return; |
| |
| if (SignalWorker(NULL, true) != 0 || pthread_join(thread_, NULL) != 0) |
| pthread_kill(thread_, SIGTERM); |
| |
| pthread_cond_destroy(&work_ready_cond_); |
| pthread_cond_destroy(&work_done_cond_); |
| pthread_mutex_destroy(&lock_); |
| } |
| |
| #define TRY(x, n, g) \ |
| ret = x; \ |
| if (ret) { \ |
| ALOGE("Failed to " n " %d", ret); \ |
| g; \ |
| } |
| |
| #define TRY_RETURN(x, n) TRY(x, n, return ret) |
| |
| int GLWorker::Init() { |
| int ret = 0; |
| |
| worker_work_ = NULL; |
| worker_exit_ = false; |
| worker_ret_ = -1; |
| |
| ret = pthread_cond_init(&work_ready_cond_, NULL); |
| if (ret) { |
| ALOGE("Failed to int GLThread condition %d", ret); |
| return ret; |
| } |
| |
| ret = pthread_cond_init(&work_done_cond_, NULL); |
| if (ret) { |
| ALOGE("Failed to int GLThread condition %d", ret); |
| pthread_cond_destroy(&work_ready_cond_); |
| return ret; |
| } |
| |
| ret = pthread_mutex_init(&lock_, NULL); |
| if (ret) { |
| ALOGE("Failed to init GLThread lock %d", ret); |
| pthread_cond_destroy(&work_ready_cond_); |
| pthread_cond_destroy(&work_done_cond_); |
| return ret; |
| } |
| |
| ret = pthread_create(&thread_, NULL, StartRoutine, this); |
| if (ret) { |
| ALOGE("Failed to create GLThread %d", ret); |
| pthread_cond_destroy(&work_ready_cond_); |
| pthread_cond_destroy(&work_done_cond_); |
| pthread_mutex_destroy(&lock_); |
| return ret; |
| } |
| |
| initialized_ = true; |
| |
| TRY_RETURN(pthread_mutex_lock(&lock_), "lock GLThread"); |
| |
| while (!worker_exit_ && worker_ret_ != 0) |
| TRY(pthread_cond_wait(&work_done_cond_, &lock_), "wait on condition", |
| goto out_unlock); |
| |
| ret = worker_ret_; |
| |
| out_unlock: |
| int unlock_ret = pthread_mutex_unlock(&lock_); |
| if (unlock_ret) { |
| ret = unlock_ret; |
| ALOGE("Failed to unlock GLThread %d", unlock_ret); |
| } |
| return ret; |
| } |
| |
| int GLWorker::SignalWorker(Work *work, bool worker_exit) { |
| int ret = 0; |
| if (worker_exit_) |
| return -EINVAL; |
| TRY_RETURN(pthread_mutex_lock(&lock_), "lock GLThread"); |
| worker_work_ = work; |
| worker_exit_ = worker_exit; |
| ret = pthread_cond_signal(&work_ready_cond_); |
| if (ret) { |
| ALOGE("Failed to signal GLThread caller %d", ret); |
| pthread_mutex_unlock(&lock_); |
| return ret; |
| } |
| ret = pthread_cond_wait(&work_done_cond_, &lock_); |
| if (ret) { |
| ALOGE("Failed to wait on GLThread %d", ret); |
| pthread_mutex_unlock(&lock_); |
| return ret; |
| } |
| |
| ret = worker_ret_; |
| if (ret) { |
| pthread_mutex_unlock(&lock_); |
| return ret; |
| } |
| TRY_RETURN(pthread_mutex_unlock(&lock_), "unlock GLThread"); |
| return ret; |
| } |
| |
| int GLWorker::DoWork(Work *work) { |
| return SignalWorker(work, false); |
| } |
| |
| void GLWorker::WorkerRoutine() { |
| int ret = 0; |
| |
| TRY(pthread_mutex_lock(&lock_), "lock GLThread", return ); |
| |
| Compositor compositor; |
| |
| TRY(compositor.Init(), "initialize GL", goto out_signal_done); |
| |
| worker_ret_ = 0; |
| TRY(pthread_cond_signal(&work_done_cond_), "signal GLThread caller", |
| goto out_signal_done); |
| |
| while (true) { |
| while (worker_work_ == NULL && !worker_exit_) |
| TRY(pthread_cond_wait(&work_ready_cond_, &lock_), "wait on condition", |
| goto out_signal_done); |
| |
| if (worker_exit_) { |
| ret = 0; |
| break; |
| } |
| |
| ret = DoComposition(compositor, worker_work_); |
| |
| worker_work_ = NULL; |
| if (ret) { |
| break; |
| } |
| } |
| |
| out_signal_done: |
| worker_exit_ = true; |
| worker_ret_ = ret; |
| TRY(pthread_cond_signal(&work_done_cond_), "signal GLThread caller", |
| goto out_unlock); |
| out_unlock: |
| TRY(pthread_mutex_unlock(&lock_), "unlock GLThread", return ); |
| } |
| |
| /* static */ |
| void *GLWorker::StartRoutine(void *arg) { |
| setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY); |
| GLWorker *worker = (GLWorker *)arg; |
| worker->WorkerRoutine(); |
| return NULL; |
| } |
| |
| } // namespace android |