renderthread: add EGL_EXT_buffer_age support

EGL_EXT_buffer_age is better than EGL_BUFFER_PRESERVED
because it can save memory bandwidth used to blit
back buffer into front buffer.

Change-Id: I2fea0ee08dc7dd66e348b04dd694d075d509d01b
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index fe4f1be..7b2bba1 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -19,6 +19,7 @@
     renderthread/RenderTask.cpp \
     renderthread/RenderThread.cpp \
     renderthread/TimeLord.cpp \
+    renderthread/DirtyHistory.cpp \
     thread/TaskManager.cpp \
     utils/Blur.cpp \
     utils/GLUtils.cpp \
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6dfb6e8..ea73387 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -98,6 +98,9 @@
         setSurface(nullptr);
     }
     mHaveNewSurface = false;
+    if (mEglManager.useBufferAgeExt()) {
+        mDirtyHistory.prepend(Rect(dirty));
+    }
 }
 
 void CanvasContext::requireSurface() {
@@ -227,6 +230,8 @@
             "drawRenderNode called on a context with no canvas or surface!");
 
     SkRect dirty;
+    bool useBufferAgeExt = mEglManager.useBufferAgeExt();
+    Rect patchedDirty;
     mDamageAccumulator.finish(&dirty);
 
     // TODO: Re-enable after figuring out cause of b/22592975
@@ -237,12 +242,18 @@
 
     mCurrentFrameInfo->markIssueDrawCommandsStart();
 
-    EGLint width, height;
-    mEglManager.beginFrame(mEglSurface, &width, &height);
+    EGLint width, height, framebufferAge;
+    mEglManager.beginFrame(mEglSurface, &width, &height, &framebufferAge);
+
+    if (useBufferAgeExt && mHaveNewSurface) {
+        mDirtyHistory.clear();
+    }
+
     if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
         mCanvas->setViewport(width, height);
         dirty.setEmpty();
     } else if (!mBufferPreserved || mHaveNewSurface) {
+        mDirtyHistory.clear();
         dirty.setEmpty();
     } else {
         if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
@@ -253,9 +264,14 @@
         profiler().unionDirty(&dirty);
     }
 
-    if (!dirty.isEmpty()) {
-        mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
-                dirty.fRight, dirty.fBottom, mOpaque);
+    patchedDirty = dirty;
+    if (useBufferAgeExt && !dirty.isEmpty()) {
+        patchedDirty = mDirtyHistory.unionWith(Rect(dirty), framebufferAge-1);
+    }
+
+    if (!patchedDirty.isEmpty()) {
+        mCanvas->prepareDirty(patchedDirty.left, patchedDirty.top,
+                patchedDirty.right, patchedDirty.bottom, mOpaque);
     } else {
         mCanvas->prepare(mOpaque);
     }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 88c18a5..59f9c3a 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -25,6 +25,7 @@
 #include "utils/RingBuffer.h"
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
+#include "renderthread/DirtyHistory.h"
 
 #include <cutils/compiler.h>
 #include <EGL/egl.h>
@@ -146,6 +147,8 @@
     FrameInfoVisualizer mProfiler;
 
     std::set<RenderNode*> mPrefetechedLayers;
+
+    DirtyHistory mDirtyHistory;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DirtyHistory.cpp b/libs/hwui/renderthread/DirtyHistory.cpp
new file mode 100644
index 0000000..1419e84
--- /dev/null
+++ b/libs/hwui/renderthread/DirtyHistory.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+#include "DirtyHistory.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+DirtyHistory::DirtyHistory()
+        : mBack(DIRTY_HISTORY_SIZE - 1) {
+    clear();
+}
+
+void DirtyHistory::clear()
+{
+    for (int i = 0; i < DIRTY_HISTORY_SIZE; i++) {
+        mHistory[i].clear();
+    }
+}
+
+Rect DirtyHistory::get(int index) {
+    if (index >= DIRTY_HISTORY_SIZE || index < 0)
+        return Rect();
+    return mHistory[(1 + mBack + index) % DIRTY_HISTORY_SIZE];
+}
+
+Rect DirtyHistory::unionWith(Rect rect, int count) {
+    if (rect.isEmpty() || count > DIRTY_HISTORY_SIZE || count < 0)
+        return Rect();
+
+    for (int i = 0; i < count; i++) {
+        Rect ith = get(i);
+        if (ith.isEmpty())
+            return Rect();
+
+        // rect union
+        rect.left = fminf(rect.left, ith.left);
+        rect.top = fminf(rect.top, ith.top);
+        rect.right = fmaxf(rect.right, ith.right);
+        rect.bottom = fmaxf(rect.bottom, ith.bottom);
+    }
+    return rect;
+}
+
+void DirtyHistory::prepend(Rect rect) {
+    if (rect.isEmpty()) {
+        mHistory[mBack].clear();
+    } else {
+        mHistory[mBack].set(rect);
+    }
+    mBack = (mBack + DIRTY_HISTORY_SIZE - 1) % DIRTY_HISTORY_SIZE;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/DirtyHistory.h b/libs/hwui/renderthread/DirtyHistory.h
new file mode 100644
index 0000000..d5ea597
--- /dev/null
+++ b/libs/hwui/renderthread/DirtyHistory.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+#ifndef DIRTYHISTORY_H
+#define DIRTYHISTORY_H
+
+#include <Rect.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+#define DIRTY_HISTORY_SIZE 4
+
+class DirtyHistory {
+public:
+    DirtyHistory();
+    ~DirtyHistory() {}
+
+    Rect get(int index);
+    Rect unionWith(Rect rect, int count);
+    void prepend(Rect rect);
+    void clear();
+private:
+    Rect mHistory[DIRTY_HISTORY_SIZE];
+    int mBack;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DIRTYHISTORY_H */
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index eb332d5..ac36f53 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -76,6 +76,7 @@
         , mEglContext(EGL_NO_CONTEXT)
         , mPBufferSurface(EGL_NO_SURFACE)
         , mAllowPreserveBuffer(load_dirty_regions_property())
+        , mHasBufferAgeExt(false)
         , mCurrentSurface(EGL_NO_SURFACE)
         , mAtlasMap(nullptr)
         , mAtlasMapSize(0) {
@@ -98,7 +99,10 @@
 
     ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
 
-    loadConfig();
+    findExtensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS), mEglExtensionList);
+    mHasBufferAgeExt = hasEglExtension("EGL_EXT_buffer_age");
+
+    loadConfig(mHasBufferAgeExt);
     createContext();
     createPBufferSurface();
     makeCurrent(mPBufferSurface);
@@ -110,8 +114,13 @@
     return mEglDisplay != EGL_NO_DISPLAY;
 }
 
-void EglManager::loadConfig() {
-    EGLint swapBehavior = mCanSetPreserveBuffer ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+bool EglManager::hasEglExtension(const char* extension) const {
+   const std::string s(extension);
+   return mEglExtensionList.find(s) != mEglExtensionList.end();
+}
+
+void EglManager::loadConfig(bool useBufferAgeExt) {
+    EGLint swapBehavior = (!useBufferAgeExt && mCanSetPreserveBuffer) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
     EGLint attribs[] = {
             EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
             EGL_RED_SIZE, 8,
@@ -133,7 +142,7 @@
             ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
             // Try again without dirty regions enabled
             mCanSetPreserveBuffer = false;
-            loadConfig();
+            loadConfig(useBufferAgeExt);
         } else {
             LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
         }
@@ -238,7 +247,7 @@
     return true;
 }
 
-void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
+void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height, EGLint* framebufferAge) {
     LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
             "Tried to beginFrame on EGL_NO_SURFACE!");
     makeCurrent(surface);
@@ -248,6 +257,9 @@
     if (height) {
         eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
     }
+    if (useBufferAgeExt()) {
+        eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, framebufferAge);
+    }
     eglBeginFrame(mEglDisplay, surface);
 }
 
@@ -304,6 +316,10 @@
     return false;
 }
 
+bool EglManager::useBufferAgeExt() {
+    return mAllowPreserveBuffer && mHasBufferAgeExt;
+}
+
 void EglManager::fence() {
     EGLSyncKHR fence = eglCreateSyncKHR(mEglDisplay, EGL_SYNC_FENCE_KHR, NULL);
     eglClientWaitSyncKHR(mEglDisplay, fence,
@@ -314,6 +330,9 @@
 bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) {
     if (CC_UNLIKELY(!mAllowPreserveBuffer)) return false;
 
+    // Use EGL_EXT_buffer_age instead if supported
+    if (mHasBufferAgeExt) return true;
+
     bool preserved = false;
     if (mCanSetPreserveBuffer) {
         preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
@@ -337,6 +356,19 @@
     return preserved;
 }
 
+void EglManager::findExtensions(const char* extensions, std::set<std::string>& list) const {
+    const char* current = extensions;
+    const char* head = current;
+    do {
+        head = strchr(current, ' ');
+        std::string s(current, head ? head - current : strlen(current));
+        if (s.length()) {
+            list.insert(s);
+        }
+        current = head + 1;
+    } while (head);
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 0a8cfd3..bb5d24b 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -21,6 +21,7 @@
 #include <SkRect.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/StrongPointer.h>
+#include <set>
 
 namespace android {
 namespace uirenderer {
@@ -37,6 +38,8 @@
 
     bool hasEglContext();
 
+    bool hasEglExtension(const char* extension) const;
+
     EGLSurface createSurface(EGLNativeWindowType window);
     void destroySurface(EGLSurface surface);
 
@@ -45,12 +48,14 @@
     bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
     // Returns true if the current surface changed, false if it was already current
     bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr);
-    void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
+    void beginFrame(EGLSurface surface, EGLint* width, EGLint* height, EGLint* framebufferAge);
     bool swapBuffers(EGLSurface surface, const SkRect& dirty, EGLint width, EGLint height);
 
     // Returns true iff the surface is now preserving buffers.
     bool setPreserveBuffer(EGLSurface surface, bool preserve);
 
+    bool useBufferAgeExt();
+
     void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
 
     void fence();
@@ -63,10 +68,12 @@
     ~EglManager();
 
     void createPBufferSurface();
-    void loadConfig();
+    void loadConfig(bool useBufferAgeExt);
     void createContext();
     void initAtlas();
 
+    void findExtensions(const char* extensions, std::set<std::string>& list) const;
+
     RenderThread& mRenderThread;
 
     EGLDisplay mEglDisplay;
@@ -77,11 +84,15 @@
     const bool mAllowPreserveBuffer;
     bool mCanSetPreserveBuffer;
 
+    bool mHasBufferAgeExt;
+
     EGLSurface mCurrentSurface;
 
     sp<GraphicBuffer> mAtlasBuffer;
     int64_t* mAtlasMap;
     size_t mAtlasMapSize;
+
+    std::set<std::string> mEglExtensionList;
 };
 
 } /* namespace renderthread */