/* * Copyright 2018 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 <gui/BufferItemConsumer.h> #include <gui/Surface.h> #include <GLES3/gl3.h> #include <math/vec2.h> #include <math/vec3.h> #include <math/vec4.h> #include "BufferGenerator.h" #include "BufferGeneratorShader.h" namespace android { /* Used to receive the surfaces and fences from egl. The egl buffers are thrown * away. The fences are sent to the requester via a callback */ class SurfaceManager { public: /* Returns a fence from egl */ using BufferCallback = std::function<void(const sp<GraphicBuffer>& buffer, int32_t fence)>; /* Listens for a new frame, detaches the buffer and returns the fence * through saved callback. */ class BufferListener : public ConsumerBase::FrameAvailableListener { public: BufferListener(sp<IGraphicBufferConsumer> consumer, BufferCallback callback) : mConsumer(consumer), mCallback(callback) {} void onFrameAvailable(const BufferItem& /*item*/) { BufferItem item; if (mConsumer->acquireBuffer(&item, 0)) return; if (mConsumer->detachBuffer(item.mSlot)) return; mCallback(item.mGraphicBuffer, item.mFence->dup()); } private: sp<IGraphicBufferConsumer> mConsumer; BufferCallback mCallback; }; /* Creates a buffer listener that waits on a new frame from the buffer * queue. */ void initialize(uint32_t width, uint32_t height, android_pixel_format_t format, BufferCallback callback) { sp<IGraphicBufferProducer> producer; sp<IGraphicBufferConsumer> consumer; BufferQueue::createBufferQueue(&producer, &consumer); consumer->setDefaultBufferSize(width, height); consumer->setDefaultBufferFormat(format); mBufferItemConsumer = new BufferItemConsumer(consumer, 0); mListener = new BufferListener(consumer, callback); mBufferItemConsumer->setFrameAvailableListener(mListener); mSurface = new Surface(producer, true); } /* Used by Egl manager. The surface is never displayed. */ sp<Surface> getSurface() const { return mSurface; } private: sp<BufferItemConsumer> mBufferItemConsumer; sp<BufferListener> mListener; /* Used by Egl manager. The surface is never displayed */ sp<Surface> mSurface; }; /* Used to generate valid fences. It is not possible to create a dummy sync * fence for testing. Egl can generate buffers along with a valid fence. * The buffer cannot be guaranteed to be the same format across all devices so * a CPU filled buffer is used instead. The Egl fence is used along with the * CPU filled buffer. */ class EglManager { public: EglManager() : mEglDisplay(EGL_NO_DISPLAY), mEglSurface(EGL_NO_SURFACE), mEglContext(EGL_NO_CONTEXT) {} ~EglManager() { cleanup(); } int initialize(sp<Surface> surface) { mSurface = surface; mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (mEglDisplay == EGL_NO_DISPLAY) return false; EGLint major; EGLint minor; if (!eglInitialize(mEglDisplay, &major, &minor)) { ALOGW("Could not initialize EGL"); return false; } /* We're going to use a 1x1 pbuffer surface later on * The configuration distance doesn't really matter for what we're * trying to do */ EGLint configAttrs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 0, EGL_NONE}; EGLConfig configs[1]; EGLint configCnt; if (!eglChooseConfig(mEglDisplay, configAttrs, configs, 1, &configCnt)) { ALOGW("Could not select EGL configuration"); eglReleaseThread(); eglTerminate(mEglDisplay); return false; } if (configCnt <= 0) { ALOGW("Could not find EGL configuration"); eglReleaseThread(); eglTerminate(mEglDisplay); return false; } /* These objects are initialized below but the default "null" values are * used to cleanup properly at any point in the initialization sequence */ EGLint attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; mEglContext = eglCreateContext(mEglDisplay, configs[0], EGL_NO_CONTEXT, attrs); if (mEglContext == EGL_NO_CONTEXT) { ALOGW("Could not create EGL context"); cleanup(); return false; } EGLint majorVersion; if (!eglQueryContext(mEglDisplay, mEglContext, EGL_CONTEXT_CLIENT_VERSION, &majorVersion)) { ALOGW("Could not query EGL version"); cleanup(); return false; } if (majorVersion != 3) { ALOGW("Unsupported EGL version"); cleanup(); return false; } EGLint surfaceAttrs[] = {EGL_NONE}; mEglSurface = eglCreateWindowSurface(mEglDisplay, configs[0], mSurface.get(), surfaceAttrs); if (mEglSurface == EGL_NO_SURFACE) { ALOGW("Could not create EGL surface"); cleanup(); return false; } if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { ALOGW("Could not change current EGL context"); cleanup(); return false; } return true; } void makeCurrent() const { eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); } void present() const { eglSwapBuffers(mEglDisplay, mEglSurface); } private: void cleanup() { if (mEglDisplay == EGL_NO_DISPLAY) return; if (mEglSurface != EGL_NO_SURFACE) eglDestroySurface(mEglDisplay, mEglSurface); if (mEglContext != EGL_NO_CONTEXT) eglDestroyContext(mEglDisplay, mEglContext); eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglReleaseThread(); eglTerminate(mEglDisplay); } sp<Surface> mSurface; EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLContext mEglContext; }; class Program { public: ~Program() { if (mInitialized) { glDetachShader(mProgram, mVertexShader); glDetachShader(mProgram, mFragmentShader); glDeleteShader(mVertexShader); glDeleteShader(mFragmentShader); glDeleteProgram(mProgram); } } bool initialize(const char* vertex, const char* fragment) { mVertexShader = buildShader(vertex, GL_VERTEX_SHADER); if (!mVertexShader) { return false; } mFragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); if (!mFragmentShader) { return false; } mProgram = glCreateProgram(); glAttachShader(mProgram, mVertexShader); glAttachShader(mProgram, mFragmentShader); glLinkProgram(mProgram); GLint status; glGetProgramiv(mProgram, GL_LINK_STATUS, &status); if (status != GL_TRUE) { GLint length = 0; glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &length); if (length > 1) { GLchar log[length]; glGetProgramInfoLog(mProgram, length, nullptr, &log[0]); ALOGE("%s", log); } ALOGE("Error while linking shaders"); return false; } mInitialized = true; return true; } void use() const { glUseProgram(mProgram); } void bindVec4(GLint location, vec4 v) const { glUniform4f(location, v.x, v.y, v.z, v.w); } void bindVec3(GLint location, const vec3* v, uint32_t count) const { glUniform3fv(location, count, &(v->x)); } void bindFloat(GLint location, float v) { glUniform1f(location, v); } private: GLuint buildShader(const char* source, GLenum type) const { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, nullptr); glCompileShader(shader); GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { ALOGE("Error while compiling shader of type 0x%x:\n===\n%s\n===", type, source); // Some drivers return wrong values for GL_INFO_LOG_LENGTH // use a fixed size instead GLchar log[512]; glGetShaderInfoLog(shader, sizeof(log), nullptr, &log[0]); ALOGE("Shader info log: %s", log); return 0; } return shader; } GLuint mProgram = 0; GLuint mVertexShader = 0; GLuint mFragmentShader = 0; bool mInitialized = false; }; BufferGenerator::BufferGenerator() : mSurfaceManager(new SurfaceManager), mEglManager(new EglManager), mProgram(new Program) { const float width = 1000.0; const float height = 1000.0; auto setBufferWithContext = std::bind(setBuffer, std::placeholders::_1, std::placeholders::_2, this); mSurfaceManager->initialize(width, height, HAL_PIXEL_FORMAT_RGBA_8888, setBufferWithContext); if (!mEglManager->initialize(mSurfaceManager->getSurface())) return; mEglManager->makeCurrent(); if (!mProgram->initialize(VERTEX_SHADER, FRAGMENT_SHADER)) return; mProgram->use(); mProgram->bindVec4(0, vec4{width, height, 1.0f / width, 1.0f / height}); mProgram->bindVec3(2, &SPHERICAL_HARMONICS[0], 4); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, &TRIANGLE[0]); mInitialized = true; } BufferGenerator::~BufferGenerator() { mEglManager->makeCurrent(); } status_t BufferGenerator::get(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence) { // mMutex is used to protect get() from getting called by multiple threads at the same time static std::mutex mMutex; std::lock_guard lock(mMutex); if (!mInitialized) { if (outBuffer) { *outBuffer = nullptr; } if (*outFence) { *outFence = nullptr; } return -EINVAL; } // Generate a buffer and fence. They will be returned through the setBuffer callback mEglManager->makeCurrent(); glClear(GL_COLOR_BUFFER_BIT); const std::chrono::duration<float> time = std::chrono::steady_clock::now() - mEpoch; mProgram->bindFloat(1, time.count()); glDrawArrays(GL_TRIANGLES, 0, 3); mPending = true; mEglManager->present(); // Wait for the setBuffer callback if (!mConditionVariable.wait_for(mMutex, std::chrono::seconds(2), [this] { return !mPending; })) { ALOGE("failed to set buffer and fence"); return -ETIME; } // Return buffer and fence if (outBuffer) { *outBuffer = mGraphicBuffer; } if (outFence) { *outFence = new Fence(mFence); } else { close(mFence); } mGraphicBuffer = nullptr; mFence = -1; return NO_ERROR; } // static void BufferGenerator::setBuffer(const sp<GraphicBuffer>& buffer, int32_t fence, void* bufferGenerator) { BufferGenerator* generator = static_cast<BufferGenerator*>(bufferGenerator); generator->mGraphicBuffer = buffer; generator->mFence = fence; generator->mPending = false; generator->mConditionVariable.notify_all(); } } // namespace android