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/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 */