Optimize saveLayer() when the clip flag is set.
This speeds up applications, especially Launcher.
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
index 77fbda2..2ef71c2 100644
--- a/libs/hwui/FboCache.cpp
+++ b/libs/hwui/FboCache.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "OpenGLRenderer"
+#include <stdlib.h>
+
#include "FboCache.h"
#include "Properties.h"
@@ -57,14 +59,31 @@
///////////////////////////////////////////////////////////////////////////////
void FboCache::clear() {
-
+ for (size_t i = 0; i < mCache.size(); i++) {
+ const GLuint fbo = mCache.itemAt(i);
+ glDeleteFramebuffers(1, &fbo);
+ }
+ mCache.clear();
}
GLuint FboCache::get() {
- return 0;
+ GLuint fbo;
+ if (mCache.size() > 0) {
+ fbo = mCache.itemAt(mCache.size() - 1);
+ mCache.removeAt(mCache.size() - 1);
+ } else {
+ glGenFramebuffers(1, &fbo);
+ }
+ return fbo;
}
bool FboCache::put(GLuint fbo) {
+ if (mCache.size() < mMaxSize) {
+ mCache.add(fbo);
+ return true;
+ }
+
+ glDeleteFramebuffers(1, &fbo);
return false;
}
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
index 66f66ea..ec4afe9 100644
--- a/libs/hwui/FboCache.h
+++ b/libs/hwui/FboCache.h
@@ -21,8 +21,6 @@
#include <utils/SortedVector.h>
-#include "GenerationCache.h"
-
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index 070e33f..35c6bea 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -65,6 +65,7 @@
void put(K key, V value);
V remove(K key);
V removeOldest();
+ V getValueAt(uint32_t index) const;
uint32_t size() const;
@@ -128,6 +129,11 @@
}
template<typename K, typename V>
+V GenerationCache<K, V>::getValueAt(uint32_t index) const {
+ return mCache.valueAt(index);
+}
+
+template<typename K, typename V>
V GenerationCache<K, V>::get(K key) {
ssize_t index = mCache.indexOfKey(key);
if (index >= 0) {
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index a0cc5d6..6024765 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -32,21 +32,14 @@
* Dimensions of a layer.
*/
struct LayerSize {
- LayerSize(): width(0), height(0), id(0) { }
- LayerSize(const uint32_t width, const uint32_t height): width(width), height(height), id(0) { }
- LayerSize(const LayerSize& size): width(size.width), height(size.height), id(size.id) { }
+ LayerSize(): width(0), height(0) { }
+ LayerSize(const uint32_t width, const uint32_t height): width(width), height(height) { }
+ LayerSize(const LayerSize& size): width(size.width), height(size.height) { }
uint32_t width;
uint32_t height;
- // Incremental id used by the layer cache to store multiple
- // LayerSize with the same dimensions
- uint32_t id;
-
bool operator<(const LayerSize& rhs) const {
- if (id != 0 && rhs.id != 0 && id != rhs.id) {
- return id < rhs.id;
- }
if (width == rhs.width) {
return height < rhs.height;
}
@@ -54,12 +47,12 @@
}
bool operator==(const LayerSize& rhs) const {
- return id == rhs.id && width == rhs.width && height == rhs.height;
+ return width == rhs.width && height == rhs.height;
}
}; // struct LayerSize
/**
- * A layer has dimensions and is backed by an OpenGL texture.
+ * A layer has dimensions and is backed by an OpenGL texture or FBO.
*/
struct Layer {
/**
@@ -71,6 +64,11 @@
*/
GLuint texture;
/**
+ * Name of the FBO used to render the layer. If the name is 0
+ * this layer is not backed by an FBO, but a simple texture.
+ */
+ GLuint fbo;
+ /**
* Opacity of the layer.
*/
int alpha;
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 8c70cf9..2183718 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -32,7 +32,7 @@
LayerCache::LayerCache():
mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
- mIdGenerator(1), mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+ mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
char property[PROPERTY_VALUE_MAX];
if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
LOGD(" Setting layer cache size to %sMB", property);
@@ -44,7 +44,7 @@
LayerCache::LayerCache(uint32_t maxByteSize):
mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
- mIdGenerator(1), mSize(0), mMaxSize(maxByteSize) {
+ mSize(0), mMaxSize(maxByteSize) {
}
LayerCache::~LayerCache() {
@@ -110,6 +110,7 @@
layer = new Layer;
layer->blend = true;
layer->empty = true;
+ layer->fbo = 0;
glGenTextures(1, &layer->texture);
glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -121,6 +122,14 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#if DEBUG_LAYERS
+ uint32_t size = mCache.size();
+ for (uint32_t i = 0; i < size; i++) {
+ LayerSize ls = mCache.getKeyAt(i);
+ LAYER_LOGD(" Layer size %dx%d", ls.width, ls.height);
+ }
+#endif
}
return layer;
@@ -133,9 +142,10 @@
while (mSize + size > mMaxSize) {
Layer* oldest = mCache.removeOldest();
deleteLayer(oldest);
+ LAYER_LOGD(" Deleting layer %.2fx%.2f", oldest->layer.getWidth(),
+ oldest->layer.getHeight());
}
- layerSize.id = mIdGenerator++;
mCache.put(layerSize, layer);
mSize += size;
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index c0c7542..cbb7ae2 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -64,6 +64,7 @@
* @param size The dimensions of the desired layer
*/
Layer* get(LayerSize& size);
+
/**
* Adds the layer to the cache. The layer will not be added if there is
* not enough space available.
@@ -96,7 +97,6 @@
void deleteLayer(Layer* layer);
GenerationCache<LayerSize, Layer*> mCache;
- uint32_t mIdGenerator;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e3790f5..ee5fe22 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -145,6 +145,9 @@
mWidth = width;
mHeight = height;
+
+ mFirstSnapshot->height = height;
+ mFirstSnapshot->viewport.set(0, 0, width, height);
}
void OpenGLRenderer::prepare() {
@@ -185,7 +188,7 @@
}
void OpenGLRenderer::releaseContext() {
- glViewport(0, 0, mWidth, mHeight);
+ glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
glEnable(GL_SCISSOR_TEST);
setScissorFromClip();
@@ -237,10 +240,17 @@
bool OpenGLRenderer::restoreSnapshot() {
bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
+ bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
sp<Snapshot> current = mSnapshot;
sp<Snapshot> previous = mSnapshot->previous;
+ if (restoreOrtho) {
+ Rect& r = previous->viewport;
+ glViewport(r.left, r.top, r.right, r.bottom);
+ mOrthoMatrix.load(current->orthoMatrix);
+ }
+
mSaveCount--;
mSnapshot = previous;
@@ -261,7 +271,8 @@
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
const SkPaint* p, int flags) {
- int count = saveSnapshot(flags);
+ const GLuint previousFbo = mSnapshot->fbo;
+ const int count = saveSnapshot(flags);
int alpha = 255;
SkXfermode::Mode mode;
@@ -281,7 +292,7 @@
mode = SkXfermode::kSrcOver_Mode;
}
- createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+ createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
return count;
}
@@ -346,17 +357,21 @@
* something actually gets drawn are the layers regions cleared.
*/
bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
- float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
- LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
+ float right, float bottom, int alpha, SkXfermode::Mode mode,
+ int flags, GLuint previousFbo) {
+ LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
+ const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+
// Window coordinates of the layer
Rect bounds(left, top, right, bottom);
- mSnapshot->transform->mapRect(bounds);
-
- // Layers only make sense if they are in the framebuffer's bounds
- bounds.intersect(*mSnapshot->clipRect);
- bounds.snapToPixelBoundaries();
+ if (!fboLayer) {
+ mSnapshot->transform->mapRect(bounds);
+ // Layers only make sense if they are in the framebuffer's bounds
+ bounds.intersect(*mSnapshot->clipRect);
+ bounds.snapToPixelBoundaries();
+ }
if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
bounds.getHeight() > mMaxTextureSize) {
@@ -379,29 +394,77 @@
snapshot->flags |= Snapshot::kFlagIsLayer;
snapshot->layer = layer;
- // Copy the framebuffer into the layer
- glBindTexture(GL_TEXTURE_2D, layer->texture);
+ if (fboLayer) {
+ layer->fbo = mCaches.fboCache.get();
- // TODO: Workaround for b/3054204
- glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
- bounds.getWidth(), bounds.getHeight(), 0);
+ snapshot->flags |= Snapshot::kFlagIsFboLayer;
+ snapshot->fbo = layer->fbo;
+ snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+ snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->height = bounds.getHeight();
+ snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+ snapshot->orthoMatrix.load(mOrthoMatrix);
- // TODO: Waiting for b/3054204 to be fixed
-// if (layer->empty) {
-// glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-// bounds.getWidth(), bounds.getHeight(), 0);
-// layer->empty = false;
-// } else {
-// glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-// bounds.getWidth(), bounds.getHeight());
-// }
-
- if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
setScissorFromClip();
- }
- // Enqueue the buffer coordinates to clear the corresponding region later
- mLayers.push(new Rect(bounds));
+ // Bind texture to FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ // Initialize the texture if needed
+ if (layer->empty) {
+ layer->empty = false;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ layer->texture, 0);
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ glDeleteTextures(1, &layer->texture);
+ mCaches.fboCache.put(layer->fbo);
+
+ delete layer;
+
+ return false;
+ }
+
+ // Clear the FBO
+ glDisable(GL_SCISSOR_TEST);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glEnable(GL_SCISSOR_TEST);
+
+ // Change the ortho projection
+ glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+ mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+ } else {
+ // Copy the framebuffer into the layer
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ // TODO: Workaround for b/3054204
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight(), 0);
+
+ // TODO: Waiting for b/3054204 to be fixed
+ // if (layer->empty) {
+ // glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ // bounds.getWidth(), bounds.getHeight(), 0);
+ // layer->empty = false;
+ // } else {
+ // glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+ // bounds.getWidth(), bounds.getHeight());
+ // }
+
+ // Enqueue the buffer coordinates to clear the corresponding region later
+ mLayers.push(new Rect(bounds));
+ }
return true;
}
@@ -415,14 +478,21 @@
return;
}
+ const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+
+ if (fboLayer) {
+ // Unbind current FBO and restore previous one
+ glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+ }
+
// Restore the clip from the previous snapshot
const Rect& clip = *previous->clipRect;
- glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
Layer* layer = current->layer;
const Rect& rect = layer->layer;
- if (layer->alpha < 255) {
+ if (!fboLayer && layer->alpha < 255) {
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
}
@@ -430,20 +500,32 @@
// Layers are already drawn with a top-left origin, don't flip the texture
resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
- drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
- 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
- &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+ if (fboLayer) {
+ drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+ layer->texture, layer->alpha / 255.0f, layer->mode, layer->blend);
+ } else {
+ drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+ 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+ &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+ }
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+ if (fboLayer) {
+ // Detach the texture from the FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+ // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
+ mCaches.fboCache.put(current->fbo);
+ }
+
LayerSize size(rect.getWidth(), rect.getHeight());
- // Failing to add the layer to the cache should happen only if the
- // layer is too large
+ // Failing to add the layer to the cache should happen only if the layer is too large
if (!mCaches.layerCache.put(size, layer)) {
LAYER_LOGD("Deleting layer");
-
glDeleteTextures(1, &layer->texture);
-
delete layer;
}
}
@@ -503,7 +585,7 @@
void OpenGLRenderer::setScissorFromClip() {
const Rect& clip = *mSnapshot->clipRect;
- glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
}
const Rect& OpenGLRenderer::getClipBounds() {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1974cf0..e3d4653 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -167,11 +167,12 @@
* @param alpha The translucency of the layer
* @param mode The blending mode of the layer
* @param flags The layer save flags
+ * @param previousFbo The name of the current framebuffer
*
* @return True if the layer was successfully created, false otherwise
*/
bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags);
+ int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
/**
* Clears all the regions corresponding to the current list of layers.
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d573805..3012824 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -45,7 +45,7 @@
#define MB(s) s * 1024 * 1024
#define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
-#define DEFAULT_LAYER_CACHE_SIZE 4.0f
+#define DEFAULT_LAYER_CACHE_SIZE 8.0f
#define DEFAULT_PATH_CACHE_SIZE 5.0f
#define DEFAULT_PATCH_CACHE_SIZE 100
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index c736a1c..3d74b4c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -43,7 +43,7 @@
*/
class Snapshot: public LightRefBase<Snapshot> {
public:
- Snapshot(): flags(0), previous(NULL), layer(NULL) {
+ Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) {
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
}
@@ -53,7 +53,8 @@
* the previous snapshot.
*/
Snapshot(const sp<Snapshot>& s, int saveFlags):
- flags(0), previous(s), layer(NULL) {
+ flags(0), previous(s), layer(NULL),
+ fbo(s->fbo), viewport(s->viewport), height(s->height) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -91,9 +92,19 @@
*/
kFlagIsLayer = 0x2,
/**
+ * Indicates that this snapshot is a special type of layer
+ * backed by an FBO. This flag only makes sense when the
+ * flag kFlagIsLayer is also set.
+ */
+ kFlagIsFboLayer = 0x4,
+ /**
* Indicates that the local clip should be recomputed.
*/
- kFlagDirtyLocalClip = 0x4,
+ kFlagDirtyLocalClip = 0x8,
+ /**
+ * Indicates that this snapshot has changed the ortho matrix.
+ */
+ kFlagDirtyOrtho = 0x10,
};
/**
@@ -169,6 +180,17 @@
return mLocalClip;
}
+ void resetTransform(float x, float y, float z) {
+ transform = &mTransformRoot;
+ transform->loadTranslate(x, y, z);
+ }
+
+ void resetClip(float left, float top, float right, float bottom) {
+ clipRect = &mClipRectRoot;
+ clipRect->set(left, top, right, bottom);
+ flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+ }
+
/**
* Dirty flags.
*/
@@ -185,6 +207,26 @@
Layer* layer;
/**
+ * Only set when the flag kFlagIsFboLayer is set.
+ */
+ GLuint fbo;
+
+ /**
+ * Current viewport.
+ */
+ Rect viewport;
+
+ /**
+ * Height of the framebuffer the snapshot is rendering into.
+ */
+ int height;
+
+ /**
+ * Contains the previous ortho matrix.
+ */
+ mat4 orthoMatrix;
+
+ /**
* Local transformation. Holds the current translation, scale and
* rotation values.
*/