blast: Queue transactions by applying client

Do not update SurfaceFlinger until all the fences in a transaction
have signaled. While waiting on the fences, place the transaction
in a queue with other transactions that were applied by the
same SurfaceComposerClient.

Test: Transaction_test
Bug: 80477568

Change-Id: I6b866bfa955d5eafef28016a0c5de7c3862f1837
diff --git a/services/surfaceflinger/tests/BufferGenerator.cpp b/services/surfaceflinger/tests/BufferGenerator.cpp
new file mode 100644
index 0000000..8ddda60
--- /dev/null
+++ b/services/surfaceflinger/tests/BufferGenerator.cpp
@@ -0,0 +1,381 @@
+/*
+ * 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