| /* |
| * 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. |
| */ |
| |
| // TODO(b/129481165): remove the #pragma below and fix conversion issues |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wconversion" |
| |
| #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, GraphicBuffer::USAGE_HW_TEXTURE); |
| |
| 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 placeholder 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) { |
| mBufferSize.set(1000.0, 1000.0); |
| |
| auto setBufferWithContext = |
| std::bind(setBuffer, std::placeholders::_1, std::placeholders::_2, this); |
| mSurfaceManager->initialize(mBufferSize.width, mBufferSize.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{mBufferSize.width, mBufferSize.height, 1.0f / mBufferSize.width, |
| 1.0f / mBufferSize.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; |
| } |
| |
| ui::Size BufferGenerator::getSize() { |
| return mBufferSize; |
| } |
| |
| // 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 |
| |
| // TODO(b/129481165): remove the #pragma below and fix conversion issues |
| #pragma clang diagnostic pop // ignored "-Wconversion" |