Document the implementation of saveLayer().

The implementation is simple but tricky. Leave explanations to my
future self so that I don't invent a time machine to come back to
now and slap my self.

This change also simplifies the way the GL blending function is
chosen when compositing a layer. It reuses existing OpenGLRenderer
APIs and is easier to understand.

Change-Id: I1b9cf8c5d51e09836d85b8cf157a1c284aa65c59
This commit is contained in:
Romain Guy
2010-09-13 18:00:09 -07:00
parent 81ab046a11
commit 1c740bce8a
2 changed files with 51 additions and 17 deletions

View File

@@ -280,7 +280,49 @@ 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 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.
*/
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);
@@ -323,6 +365,9 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
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");
@@ -337,16 +382,8 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
const Rect& rect = layer->layer;
if (layer->alpha < 255) {
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_ZERO, GL_SRC_ALPHA, GL_DST_ALPHA, GL_ZERO);
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->alpha << 24, SkXfermode::kSrcOver_Mode, true, true);
glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
if (!mCaches.blend) {
glDisable(GL_BLEND);
}
layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
}
// Layers are already drawn with a top-left origin, don't flip the texture
@@ -846,7 +883,7 @@ 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, bool ignoreBlending) {
int color, SkXfermode::Mode mode, bool ignoreTransform) {
clearLayerRegions();
// If a shader is set, preserve only the alpha
@@ -872,10 +909,8 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot
mColorFilter->describe(description, mExtensions);
}
if (!ignoreBlending) {
// Setup the blending mode
chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description);
}
// Setup the blending mode
chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description);
// Build and use the appropriate shader
useProgram(mCaches.programCache.get(description));

View File

@@ -176,8 +176,7 @@ private:
* @paran ignoreBlending True if the blending is set by the caller
*/
void drawColorRect(float left, float top, float right, float bottom,
int color, SkXfermode::Mode mode, bool ignoreTransform = false,
bool ignoreBlending = false);
int color, SkXfermode::Mode mode, bool ignoreTransform = false);
/**
* Draws a textured rectangle with the specified texture. The specified coordinates