summaryrefslogtreecommitdiffstats
path: root/libs/hwui/OpenGLRenderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/hwui/OpenGLRenderer.cpp')
-rw-r--r--libs/hwui/OpenGLRenderer.cpp226
1 files changed, 149 insertions, 77 deletions
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 6c90704..b1f5f6b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -80,6 +80,24 @@ static const Blender gBlends[] = {
{ SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }
};
+// This array contains the swapped version of each SkXfermode. For instance
+// this array's SrcOver blending mode is actually DstOver. You can refer to
+// createLayer() for more information on the purpose of this array.
+static const Blender gBlendsSwap[] = {
+ { SkXfermode::kClear_Mode, GL_ZERO, GL_ZERO },
+ { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE },
+ { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO },
+ { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE },
+ { SkXfermode::kDstOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
+ { SkXfermode::kSrcIn_Mode, GL_ZERO, GL_SRC_ALPHA },
+ { SkXfermode::kDstIn_Mode, GL_DST_ALPHA, GL_ZERO },
+ { SkXfermode::kSrcOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
+ { SkXfermode::kDstOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
+ { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
+ { SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
+ { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }
+};
+
static const GLenum gTextureUnits[] = {
GL_TEXTURE0,
GL_TEXTURE1,
@@ -122,8 +140,6 @@ void OpenGLRenderer::setViewport(int width, int height) {
mWidth = width;
mHeight = height;
- mFirstSnapshot->height = height;
- mFirstSnapshot->viewport.set(0, 0, width, height);
}
void OpenGLRenderer::prepare() {
@@ -155,14 +171,19 @@ void OpenGLRenderer::acquireContext() {
}
void OpenGLRenderer::releaseContext() {
- glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
+ glViewport(0, 0, mWidth, mHeight);
glEnable(GL_SCISSOR_TEST);
setScissorFromClip();
+ glDisable(GL_DITHER);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
if (mCaches.blend) {
glEnable(GL_BLEND);
glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
+ glBlendEquation(GL_FUNC_ADD);
} else {
glDisable(GL_BLEND);
}
@@ -202,17 +223,10 @@ int OpenGLRenderer::saveSnapshot(int flags) {
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;
@@ -253,11 +267,7 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
mode = SkXfermode::kSrcOver_Mode;
}
- if (alpha > 0 && !mSnapshot->invisible) {
- createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
- } else {
- mSnapshot->invisible = true;
- }
+ createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
return count;
}
@@ -273,79 +283,123 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot
}
}
+/**
+ * Layers are viewed by Skia are slightly different than layers in image editing
+ * programs (for instance.) When a layer is created, previously created layers
+ * and the frame buffer still receive every drawing command. For instance, if a
+ * layer is created and a shape intersecting the bounds of the layers and the
+ * framebuffer is draw, the shape will be drawn on both (unless the layer was
+ * created with the SkCanvas::kClipToLayer_SaveFlag flag.)
+ *
+ * A way to implement layers is to create an FBO for each layer, backed by an RGBA
+ * texture. Unfortunately, this is inefficient as it requires every primitive to
+ * be drawn n + 1 times, where n is the number of active layers. In practice this
+ * means, for every primitive:
+ * - Switch active frame buffer
+ * - Change viewport, clip and projection matrix
+ * - Issue the drawing
+ *
+ * Switching rendering target n + 1 times per drawn primitive is extremely costly.
+ * To avoid this, layers are implemented in a different way here.
+ *
+ * This implementation relies on the frame buffer being at least RGBA 8888. When
+ * a layer is created, only a texture is created, not an FBO. The content of the
+ * frame buffer contained within the layer's bounds is copied into this texture
+ * using glCopyTexImage2D(). The layer's region is then cleared(1) in the frame
+ * buffer and drawing continues as normal. This technique therefore treats the
+ * frame buffer as a scratch buffer for the layers.
+ *
+ * To compose the layers back onto the frame buffer, each layer texture
+ * (containing the original frame buffer data) is drawn as a simple quad over
+ * the frame buffer. The trick is that the quad is set as the composition
+ * destination in the blending equation, and the frame buffer becomes the source
+ * of the composition.
+ *
+ * Drawing layers with an alpha value requires an extra step before composition.
+ * An empty quad is drawn over the layer's region in the frame buffer. This quad
+ * is drawn with the rgba color (0,0,0,alpha). The alpha value offered by the
+ * quad is used to multiply the colors in the frame buffer. This is achieved by
+ * changing the GL blend functions for the GL_FUNC_ADD blend equation to
+ * GL_ZERO, GL_SRC_ALPHA.
+ *
+ * Because glCopyTexImage2D() can be slow, an alternative implementation might
+ * be use to draw a single clipped layer. The implementation described above
+ * is correct in every case.
+ *
+ * (1) The frame buffer is actually not cleared right away. To allow the GPU
+ * to potentially optimize series of calls to glCopyTexImage2D, the frame
+ * buffer is left untouched until the first drawing operation. Only when
+ * 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);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
+ // Window coordinates of the layer
Rect bounds(left, top, right, bottom);
- // TODO: Apply transformations and treat layers in screen coordinates
- // mSnapshot->transform->mapRect(bounds);
+ mSnapshot->transform->mapRect(bounds);
- GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0;
- LayerSize size(bounds.getWidth(), bounds.getHeight());
+ // Layers only make sense if they are in the framebuffer's bounds
+ bounds.intersect(*mSnapshot->clipRect);
+ if (bounds.isEmpty()) return false;
- Layer* layer = mCaches.layerCache.get(size, previousFbo);
+ LayerSize size(bounds.getWidth(), bounds.getHeight());
+ Layer* layer = mCaches.layerCache.get(size);
if (!layer) {
return false;
}
- glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-
- // 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);
-
layer->mode = mode;
- layer->alpha = alpha / 255.0f;
+ layer->alpha = alpha;
layer->layer.set(bounds);
// Save the layer in the snapshot
snapshot->flags |= Snapshot::kFlagIsLayer;
snapshot->layer = layer;
- snapshot->fbo = layer->fbo;
- // TODO: Temporary until real layer support is implemented
- snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
- // TODO: Temporary until real layer support is implemented
- 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);
- setScissorFromClip();
+ // Copy the framebuffer into the layer
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight(), 0);
+
+ if (flags & SkCanvas::kClipToLayer_SaveFlag) {
+ if (mSnapshot->clipTransformed(bounds)) setScissorFromClip();
+ }
- // 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);
+ // Enqueue the buffer coordinates to clear the corresponding region later
+ mLayers.push(new Rect(bounds));
return true;
}
+/**
+ * Read the documentation of createLayer() before doing anything in this method.
+ */
void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
if (!current->layer) {
LOGE("Attempting to compose a layer that does not exist");
return;
}
- // Unbind current FBO and restore previous one
- // Most of the time, previous->fbo will be 0 to bind the default buffer
- glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
-
// Restore the clip from the previous snapshot
const Rect& clip = *previous->clipRect;
- glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
Layer* layer = current->layer;
const Rect& rect = layer->layer;
- // FBOs are already drawn with a top-left origin, don't flip the texture
+ if (layer->alpha < 255) {
+ drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
+ layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
+ }
+
+ // Layers are already drawn with a top-left origin, don't flip the texture
resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
- drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
- layer->texture, layer->alpha, layer->mode, layer->blend);
+ 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], NULL, 0, true, true);
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
@@ -355,13 +409,32 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
if (!mCaches.layerCache.put(size, layer)) {
LAYER_LOGD("Deleting layer");
- glDeleteFramebuffers(1, &layer->fbo);
glDeleteTextures(1, &layer->texture);
delete layer;
}
}
+void OpenGLRenderer::clearLayerRegions() {
+ if (mLayers.size() == 0) return;
+
+ for (uint32_t i = 0; i < mLayers.size(); i++) {
+ Rect* bounds = mLayers.itemAt(i);
+
+ // Clear the framebuffer where the layer will draw
+ glScissor(bounds->left, mHeight - bounds->bottom,
+ bounds->getWidth(), bounds->getHeight());
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ delete bounds;
+ }
+ mLayers.clear();
+
+ // Restore the clip
+ setScissorFromClip();
+}
+
///////////////////////////////////////////////////////////////////////////////
// Transforms
///////////////////////////////////////////////////////////////////////////////
@@ -397,7 +470,7 @@ void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
void OpenGLRenderer::setScissorFromClip() {
const Rect& clip = *mSnapshot->clipRect;
- glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
}
const Rect& OpenGLRenderer::getClipBounds() {
@@ -405,8 +478,6 @@ const Rect& OpenGLRenderer::getClipBounds() {
}
bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) {
- if (mSnapshot->invisible) return true;
-
Rect r(left, top, right, bottom);
mSnapshot->transform->mapRect(r);
return !mSnapshot->clipRect->intersects(r);
@@ -503,6 +574,7 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
Patch* mesh = mCaches.patchCache.get(patch);
mesh->updateVertices(bitmap, left, top, right, bottom,
&patch->xDivs[0], &patch->yDivs[0], patch->numXDivs, patch->numYDivs);
+ mesh->dump();
// Specify right and bottom as +1.0f from left/top to prevent scaling since the
// patch mesh already defines the final size
@@ -512,7 +584,6 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
}
void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
- if (mSnapshot->invisible) return;
const Rect& clip = *mSnapshot->clipRect;
drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
}
@@ -545,18 +616,10 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
float x, float y, SkPaint* paint) {
- if (mSnapshot->invisible || text == NULL || count == 0 ||
- (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
return;
}
-
- float scaleX = paint->getTextScaleX();
- bool applyScaleX = scaleX < 0.9999f || scaleX > 1.0001f;
- if (applyScaleX) {
- save(SkCanvas::kMatrix_SaveFlag);
- translate(x - (x * scaleX), 0.0f);
- scale(scaleX, 1.0f);
- }
+ paint->setAntiAlias(true);
float length = -1.0f;
switch (paint->getTextAlign()) {
@@ -606,21 +669,16 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
mode, false, true);
const Rect& clip = mSnapshot->getLocalClip();
+ clearLayerRegions();
fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
drawTextDecorations(text, bytesCount, length, x, y, paint);
-
- if (applyScaleX) {
- restore();
- }
}
void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
- if (mSnapshot->invisible) return;
-
GLuint textureUnit = 0;
glActiveTexture(gTextureUnits[textureUnit]);
@@ -647,6 +705,8 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
setupTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true, true);
+ clearLayerRegions();
+
// Draw the mesh
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
@@ -778,6 +838,7 @@ void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t
}
}
+// Same values used by Skia
#define kStdStrikeThru_Offset (-6.0f / 21.0f)
#define kStdUnderline_Offset (1.0f / 9.0f)
#define kStdUnderline_Thickness (1.0f / 18.0f)
@@ -831,6 +892,8 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float
void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
int color, SkXfermode::Mode mode, bool ignoreTransform) {
+ clearLayerRegions();
+
// If a shader is set, preserve only the alpha
if (mShader) {
color |= 0x00ffffff;
@@ -905,7 +968,10 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b
void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
- GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) {
+ GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount,
+ bool swapSrcDst, bool ignoreTransform) {
+ clearLayerRegions();
+
ProgramDescription description;
description.hasTexture = true;
if (mColorFilter) {
@@ -915,10 +981,15 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
mModelView.loadTranslate(left, top, 0.0f);
mModelView.scale(right - left, bottom - top, 1.0f);
- chooseBlending(blend || alpha < 1.0f, mode, description);
+ chooseBlending(blend || alpha < 1.0f, mode, description, swapSrcDst);
useProgram(mCaches.programCache.get(description));
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ if (!ignoreTransform) {
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ } else {
+ mat4 m;
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, m);
+ }
// Texture
bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0);
@@ -948,7 +1019,7 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
}
void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
- ProgramDescription& description) {
+ ProgramDescription& description, bool swapSrcDst) {
blend = blend || mode != SkXfermode::kSrcOver_Mode;
if (blend) {
if (mode < SkXfermode::kPlus_Mode) {
@@ -956,8 +1027,8 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
glEnable(GL_BLEND);
}
- GLenum sourceMode = gBlends[mode].src;
- GLenum destMode = gBlends[mode].dst;
+ GLenum sourceMode = swapSrcDst ? gBlendsSwap[mode].src : gBlends[mode].src;
+ GLenum destMode = swapSrcDst ? gBlendsSwap[mode].dst : gBlends[mode].dst;
if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
glBlendFunc(sourceMode, destMode);
@@ -970,6 +1041,7 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
// the blending, turn blending off here
if (mExtensions.hasFramebufferFetch()) {
description.framebufferMode = mode;
+ description.swapSrcDst = swapSrcDst;
}
if (mCaches.blend) {