Optimize FBO drawing with regions.
This optimization is currently disabled until Launcher is
modified to take advantage of it. The optimization can be
enabled by turning on RENDER_LAYERS_AS_REGIONS in the
OpenGLRenderer.h file.
Change-Id: I2fdf59d0f4dc690a3d7f712173ab8db3848b27b1
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index b167131..0f0316f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -26,6 +26,8 @@
#include <utils/Log.h>
#include <utils/StopWatch.h>
+#include <ui/Rect.h>
+
#include "OpenGLRenderer.h"
namespace android {
@@ -285,7 +287,7 @@
int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
int alpha, int flags) {
- if (alpha == 0xff) {
+ if (alpha >= 255 - ALPHA_THRESHOLD) {
return saveLayer(left, top, right, bottom, NULL, flags);
} else {
SkPaint paint;
@@ -377,7 +379,6 @@
bounds.getHeight() > mCaches.maxTextureSize) {
snapshot->invisible = true;
} else {
- // TODO: Should take the mode into account
snapshot->invisible = snapshot->previous->invisible ||
(alpha <= ALPHA_THRESHOLD && fboLayer);
}
@@ -387,8 +388,7 @@
return false;
}
- glActiveTexture(GL_TEXTURE0);
-
+ glActiveTexture(gTextureUnits[0]);
Layer* layer = mCaches.layerCache.get(bounds.getWidth(), bounds.getHeight());
if (!layer) {
return false;
@@ -405,56 +405,7 @@
snapshot->layer = layer;
if (fboLayer) {
- layer->fbo = mCaches.fboCache.get();
-
- 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);
-
- // 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, layer->width, layer->height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, NULL);
- }
-
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- layer->texture, 0);
-
-#if DEBUG_LAYERS
- 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;
- }
-#endif
-
- // Clear the FBO
- glScissor(0.0f, 0.0f, bounds.getWidth() + 1.0f, bounds.getHeight() + 1.0f);
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
-
- dirtyClip();
-
- // 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);
+ return createFboLayer(layer, bounds, snapshot, previousFbo);
} else {
// Copy the framebuffer into the layer
glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -475,6 +426,82 @@
return true;
}
+bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot,
+ GLuint previousFbo) {
+ layer->fbo = mCaches.fboCache.get();
+
+#if RENDER_LAYERS_AS_REGIONS
+ snapshot->region = &snapshot->layer->region;
+ snapshot->flags |= Snapshot::kFlagFboTarget;
+#endif
+
+ Rect clip(bounds);
+ snapshot->transform->mapRect(clip);
+ clip.intersect(*snapshot->clipRect);
+ clip.snapToPixelBoundaries();
+ clip.intersect(snapshot->previous->viewport);
+
+ mat4 inverse;
+ inverse.loadInverse(*mSnapshot->transform);
+
+ inverse.mapRect(clip);
+ clip.snapToPixelBoundaries();
+ clip.intersect(bounds);
+
+ 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->resetClip(clip.left, clip.top, clip.right, clip.bottom);
+ snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->height = bounds.getHeight();
+ snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+ snapshot->orthoMatrix.load(mOrthoMatrix);
+
+ // 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, layer->width, layer->height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ layer->texture, 0);
+
+#if DEBUG_LAYERS_AS_REGIONS
+ 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;
+ }
+#endif
+
+ // Clear the FBO, expand the clear region by 1 to get nice bilinear filtering
+ glScissor(clip.left - 1.0f, bounds.getHeight() - clip.bottom - 1.0f,
+ clip.getWidth() + 2.0f, clip.getHeight() + 2.0f);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ dirtyClip();
+
+ // 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);
+
+ return true;
+}
+
/**
* Read the documentation of createLayer() before doing anything in this method.
*/
@@ -484,42 +511,36 @@
return;
}
- const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+ const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer;
if (fboLayer) {
// Unbind current FBO and restore previous one
glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
}
- // Restore the clip from the previous snapshot
- Rect& clip(*previous->clipRect);
- clip.snapToPixelBoundaries();
- glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
-
Layer* layer = current->layer;
const Rect& rect = layer->layer;
if (!fboLayer && layer->alpha < 255) {
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
+ // Required below, composeLayerRect() will divide by 255
+ layer->alpha = 255;
}
- const Rect& texCoords = layer->texCoords;
mCaches.unbindMeshBuffer();
- resetDrawTextureTexCoords(texCoords.left, texCoords.top, texCoords.right, texCoords.bottom);
glActiveTexture(gTextureUnits[0]);
- if (fboLayer) {
- drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
- layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
- &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount);
- } 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);
+ // When the layer is stored in an FBO, we can save a bit of fillrate by
+ // drawing only the dirty region
+ if (fboLayer) {
+ dirtyLayer(rect.left, rect.top, rect.right, rect.bottom, *previous->transform);
+ composeLayerRegion(layer, rect);
+ } else {
+ dirtyLayer(rect.left, rect.top, rect.right, rect.bottom);
+ composeLayerRect(layer, rect, true);
+ }
if (fboLayer) {
// Detach the texture from the FBO
@@ -541,6 +562,159 @@
}
}
+void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) {
+ const Rect& texCoords = layer->texCoords;
+ resetDrawTextureTexCoords(texCoords.left, texCoords.top, texCoords.right, texCoords.bottom);
+
+ drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+ layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+ &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap);
+
+ resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+}
+
+void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
+#if RENDER_LAYERS_AS_REGIONS
+ if (layer->region.isRect()) {
+ composeLayerRect(layer, rect);
+ layer->region.clear();
+ return;
+ }
+
+ if (!layer->region.isEmpty()) {
+ size_t count;
+ const android::Rect* rects = layer->region.getArray(&count);
+
+ setupDraw();
+
+ ProgramDescription description;
+ description.hasTexture = true;
+
+ const float alpha = layer->alpha / 255.0f;
+ const bool setColor = description.setColor(alpha, alpha, alpha, alpha);
+ chooseBlending(layer->blend || layer->alpha < 255, layer->mode, description, false);
+
+ useProgram(mCaches.programCache.get(description));
+
+ // Texture
+ bindTexture(layer->texture);
+ glUniform1i(mCaches.currentProgram->getUniform("sampler"), 0);
+
+ // Always premultiplied
+ if (setColor) {
+ mCaches.currentProgram->setColor(alpha, alpha, alpha, alpha);
+ }
+
+ // Mesh
+ int texCoordsSlot = mCaches.currentProgram->getAttrib("texCoords");
+ glEnableVertexAttribArray(texCoordsSlot);
+
+ mModelView.loadIdentity();
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+
+ const float texX = 1.0f / float(layer->width);
+ const float texY = 1.0f / float(layer->height);
+
+ TextureVertex* mesh = mCaches.getRegionMesh();
+ GLsizei numQuads = 0;
+
+ glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
+ gMeshStride, &mesh[0].position[0]);
+ glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
+ gMeshStride, &mesh[0].texture[0]);
+
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (rect.getHeight() - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (rect.getHeight() - r->bottom) * texY;
+
+ // TODO: Reject quads outside of the clip
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+
+ numQuads++;
+
+ if (numQuads >= REGION_MESH_QUAD_COUNT) {
+ glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, NULL);
+ numQuads = 0;
+ mesh = mCaches.getRegionMesh();
+ }
+ }
+
+ if (numQuads > 0) {
+ glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, NULL);
+ }
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDisableVertexAttribArray(texCoordsSlot);
+
+#if DEBUG_LAYERS_AS_REGIONS
+ uint32_t colors[] = {
+ 0x7fff0000, 0x7f00ff00,
+ 0x7f0000ff, 0x7fff00ff,
+ };
+
+ int offset = 0;
+ int32_t top = rects[0].top;
+ int i = 0;
+
+ for (size_t i = 0; i < count; i++) {
+ if (top != rects[i].top) {
+ offset ^= 0x2;
+ top = rects[i].top;
+ }
+
+ Rect r(rects[i].left, rects[i].top, rects[i].right, rects[i].bottom);
+ drawColorRect(r.left, r.top, r.right, r.bottom, colors[offset + (i & 0x1)],
+ SkXfermode::kSrcOver_Mode);
+ }
+#endif
+
+ layer->region.clear();
+ }
+#else
+ composeLayerRect(layer, rect);
+#endif
+}
+
+void OpenGLRenderer::dirtyLayer(const float left, const float top,
+ const float right, const float bottom, const mat4 transform) {
+#if RENDER_LAYERS_AS_REGIONS
+ if ((mSnapshot->flags & Snapshot::kFlagFboTarget) && mSnapshot->region) {
+ Rect bounds(left, top, right, bottom);
+ transform.mapRect(bounds);
+ bounds.intersect(*mSnapshot->clipRect);
+ bounds.snapToPixelBoundaries();
+
+ android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ if (!dirty.isEmpty()) {
+ mSnapshot->region->orSelf(dirty);
+ }
+ }
+#endif
+}
+
+void OpenGLRenderer::dirtyLayer(const float left, const float top,
+ const float right, const float bottom) {
+#if RENDER_LAYERS_AS_REGIONS
+ if ((mSnapshot->flags & Snapshot::kFlagFboTarget) && mSnapshot->region) {
+ Rect bounds(left, top, right, bottom);
+ bounds.intersect(*mSnapshot->clipRect);
+ bounds.snapToPixelBoundaries();
+
+ android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ if (!dirty.isEmpty()) {
+ mSnapshot->region->orSelf(dirty);
+ }
+ }
+#endif
+}
+
void OpenGLRenderer::setupDraw() {
clearLayerRegions();
if (mDirtyClip) {
@@ -551,21 +725,26 @@
void OpenGLRenderer::clearLayerRegions() {
if (mLayers.size() == 0 || mSnapshot->invisible) return;
+ Rect clipRect(*mSnapshot->clipRect);
+ clipRect.snapToPixelBoundaries();
+
for (uint32_t i = 0; i < mLayers.size(); i++) {
Rect* bounds = mLayers.itemAt(i);
+ if (clipRect.intersects(*bounds)) {
+ // Clear the framebuffer where the layer will draw
+ glScissor(bounds->left, mSnapshot->height - bounds->bottom,
+ bounds->getWidth(), bounds->getHeight());
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
- // Clear the framebuffer where the layer will draw
- glScissor(bounds->left, mSnapshot->height - bounds->bottom,
- bounds->getWidth(), bounds->getHeight());
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
+ // Restore the clip
+ dirtyClip();
+ }
delete bounds;
}
- mLayers.clear();
- // Restore the clip
- dirtyClip();
+ mLayers.clear();
}
///////////////////////////////////////////////////////////////////////////////
@@ -678,7 +857,12 @@
if (!texture) return;
const AutoTexture autoCleanup(texture);
- drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint);
+ // This could be done in a cheaper way, all we need is pass the matrix
+ // to the vertex shader. The save/restore is a bit overkill.
+ save(SkCanvas::kMatrix_SaveFlag);
+ concatMatrix(matrix);
+ drawTextureRect(0.0f, 0.0f, bitmap->width(), bitmap->height(), texture, paint);
+ restore();
}
void OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
@@ -738,11 +922,21 @@
right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
if (mesh) {
- // Specify right and bottom as +1.0f from left/top to prevent scaling since the
- // patch mesh already defines the final size
- drawTextureMesh(left, top, left + 1.0f, top + 1.0f, texture->id, alpha / 255.0f,
+ // Mark the current layer dirty where we are going to draw the patch
+ if ((mSnapshot->flags & Snapshot::kFlagFboTarget) &&
+ mSnapshot->region && mesh->hasEmptyQuads) {
+ const size_t count = mesh->quads.size();
+ for (size_t i = 0; i < count; i++) {
+ Rect bounds = mesh->quads.itemAt(i);
+ dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
+ *mSnapshot->transform);
+ }
+ }
+
+ drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f,
mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
- GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer);
+ GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer,
+ true, !mesh->hasEmptyQuads);
}
}
@@ -801,6 +995,7 @@
mModelView.scale(length, strokeWidth, 1.0f);
}
mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ // TODO: Add bounds to the layer's region
if (mShader) {
mShader->updateTransforms(mCaches.currentProgram, mModelView, *mSnapshot);
@@ -904,13 +1099,34 @@
// Assume that the modelView matrix does not force scales, rotates, etc.
const bool linearFilter = mSnapshot->transform->changesBounds();
+
+ // Dimensions are set to (0,0), the layer (if any) won't be dirtied
setupTextureAlpha8(fontRenderer.getTexture(linearFilter), 0, 0, textureUnit,
x, y, r, g, b, a, mode, false, true, NULL, NULL);
const Rect& clip = mSnapshot->getLocalClip();
+ Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+#if RENDER_LAYERS_AS_REGIONS
+ bool hasLayer = (mSnapshot->flags & Snapshot::kFlagFboTarget) && mSnapshot->region;
+#else
+ bool hasLayer = false;
+#endif
mCaches.unbindMeshBuffer();
- fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
+ if (fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y,
+ hasLayer ? &bounds : NULL)) {
+#if RENDER_LAYERS_AS_REGIONS
+ if (hasLayer) {
+ mSnapshot->transform->mapRect(bounds);
+ bounds.intersect(*mSnapshot->clipRect);
+ bounds.snapToPixelBoundaries();
+
+ android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ mSnapshot->region->orSelf(dirty);
+ }
+#endif
+ }
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
@@ -1081,7 +1297,12 @@
} else {
mModelView.loadIdentity();
}
+
mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ if (width > 0 && height > 0) {
+ dirtyLayer(x, y, x + width, y + height, *mSnapshot->transform);
+ }
+
if (setColor) {
mCaches.currentProgram->setColor(r, g, b, a);
}
@@ -1214,9 +1435,11 @@
mModelView.scale(right - left, bottom - top, 1.0f);
if (!ignoreTransform) {
mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
} else {
mat4 identity;
mCaches.currentProgram->set(mOrthoMatrix, mModelView, identity);
+ dirtyLayer(left, top, right, bottom);
}
mCaches.currentProgram->setColor(r, g, b, a);
@@ -1251,7 +1474,7 @@
void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
- bool swapSrcDst, bool ignoreTransform, GLuint vbo) {
+ bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) {
setupDraw();
ProgramDescription description;
@@ -1262,16 +1485,20 @@
}
mModelView.loadTranslate(left, top, 0.0f);
- mModelView.scale(right - left, bottom - top, 1.0f);
+ if (!ignoreScale) {
+ mModelView.scale(right - left, bottom - top, 1.0f);
+ }
chooseBlending(blend || alpha < 1.0f, mode, description, swapSrcDst);
useProgram(mCaches.programCache.get(description));
if (!ignoreTransform) {
mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ if (dirty) dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
} else {
- mat4 m;
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, m);
+ mat4 identity;
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, identity);
+ if (dirty) dirtyLayer(left, top, right, bottom);
}
// Texture