Draw n-patches using OpenGL.

Currently only tested with title bars and buttons.

Change-Id: I8263a9281898dc0e943b1b8412827fe55639b9d6
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 027ed79..34da572 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -40,6 +40,7 @@
 
 #define DEFAULT_TEXTURE_CACHE_SIZE 20
 #define DEFAULT_LAYER_CACHE_SIZE 10
+#define DEFAULT_PATCH_CACHE_SIZE 100
 
 // Converts a number of mega-bytes into bytes
 #define MB(s) s * 1024 * 1024
@@ -95,7 +96,8 @@
 
 OpenGLRenderer::OpenGLRenderer():
         mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
-        mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+        mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
+        mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
     LOGD("Create OpenGLRenderer");
 
     char property[PROPERTY_VALUE_MAX];
@@ -298,7 +300,6 @@
     GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0;
     LayerSize size(right - left, bottom - top);
 
-    // TODO VERY IMPORTANT: Fix TextView to not call saveLayer() all the time
     Layer* layer = mLayerCache.get(size, previousFbo);
     if (!layer) {
         return false;
@@ -472,8 +473,110 @@
 
 void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, const SkPaint* paint) {
-    // TODO: Implement
-    LOGD("Draw 9patch, paddingLeft=%d", patch->paddingLeft);
+    const Texture* texture = mTextureCache.get(bitmap);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    const uint32_t width = patch->numXDivs;
+    const uint32_t height = patch->numYDivs;
+
+    Patch* mesh = mPatchCache.get(patch);
+
+    const uint32_t xStretchCount = (width + 1) >> 1;
+    const uint32_t yStretchCount = (height + 1) >> 1;
+
+    const int32_t* xDivs = &patch->xDivs[0];
+    const int32_t* yDivs = &patch->yDivs[0];
+
+    float xStretch = 0;
+    float yStretch = 0;
+    float xStretchTex = 0;
+    float yStretchTex = 0;
+
+    const float meshWidth = right - left;
+
+    const float bitmapWidth = float(bitmap->width());
+    const float bitmapHeight = float(bitmap->height());
+
+    if (xStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < width; i += 2) {
+            stretchSize += xDivs[i] - xDivs[i - 1];
+        }
+        xStretchTex = (stretchSize / bitmapWidth) / xStretchCount;
+        const float fixed = bitmapWidth - stretchSize;
+        xStretch = (right - left - fixed) / xStretchCount;
+    }
+
+    if (yStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < height; i += 2) {
+            stretchSize += yDivs[i] - yDivs[i - 1];
+        }
+        yStretchTex = (stretchSize / bitmapHeight) / yStretchCount;
+        const float fixed = bitmapHeight - stretchSize;
+        yStretch = (bottom - top - fixed) / yStretchCount;
+    }
+
+    float vy = 0.0f;
+    float ty = 0.0f;
+    TextureVertex* vertex = mesh->vertices;
+
+    generateVertices(vertex, 0.0f, 0.0f, xDivs, width, xStretch, xStretchTex,
+            meshWidth, bitmapWidth);
+    vertex += width + 2;
+
+    for (uint32_t y = 0; y < height; y++) {
+        if (y & 1) {
+            vy += yStretch;
+            ty += yStretchTex;
+        } else {
+            const float step = float(yDivs[y]);
+            vy += step;
+            ty += step / bitmapHeight;
+        }
+        generateVertices(vertex, vy, ty, xDivs, width, xStretch, xStretchTex,
+                meshWidth, bitmapWidth);
+        vertex += width + 2;
+    }
+
+    generateVertices(vertex, bottom - top, 1.0f, xDivs, width, xStretch, xStretchTex,
+            meshWidth, bitmapWidth);
+
+    // 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, mode, texture->blend,
+            true, &mesh->vertices[0].position[0], &mesh->vertices[0].texture[0], mesh->indices,
+            mesh->indicesCount);
+}
+
+void OpenGLRenderer::generateVertices(TextureVertex* vertex, float y, float v,
+        const int32_t xDivs[], uint32_t xCount, float xStretch, float xStretchTex,
+        float width, float widthTex) {
+    float vx = 0.0f;
+    float tx = 0.0f;
+
+    TextureVertex::set(vertex, vx, y, tx, v);
+    vertex++;
+
+    for (uint32_t x = 0; x < xCount; x++) {
+        if (x & 1) {
+            vx += xStretch;
+            tx += xStretchTex;
+        } else {
+            const float step = float(xDivs[x]);
+            vx += step;
+            tx += step / widthTex;
+        }
+
+        TextureVertex::set(vertex, vx, y, tx, v);
+        vertex++;
+    }
+
+    TextureVertex::set(vertex, width, y, 1.0f, v);
+    vertex++;
 }
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
@@ -538,6 +641,13 @@
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
         GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied) {
+    drawTextureMesh(left, top, right, bottom, texture, alpha, mode, blend, isPremultiplied,
+            &mDrawTextureVertices[0].position[0], &mDrawTextureVertices[0].texture[0], NULL);
+}
+
+void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
+        GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied,
+        GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) {
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
 
@@ -560,20 +670,21 @@
     glActiveTexture(GL_TEXTURE0);
     glUniform1i(mDrawTextureShader->sampler, 0);
 
-    const GLvoid* p = &mDrawTextureVertices[0].position[0];
-    const GLvoid* t = &mDrawTextureVertices[0].texture[0];
-
     glEnableVertexAttribArray(mDrawTextureShader->position);
     glVertexAttribPointer(mDrawTextureShader->position, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, p);
+            gDrawTextureVertexStride, vertices);
 
     glEnableVertexAttribArray(mDrawTextureShader->texCoords);
     glVertexAttribPointer(mDrawTextureShader->texCoords, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, t);
+            gDrawTextureVertexStride, texCoords);
 
     glVertexAttrib4f(mDrawTextureShader->color, 1.0f, 1.0f, 1.0f, alpha);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawTextureVertexCount);
+    if (!indices) {
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawTextureVertexCount);
+    } else {
+        glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_SHORT, indices);
+    }
 
     glDisableVertexAttribArray(mDrawTextureShader->position);
     glDisableVertexAttribArray(mDrawTextureShader->texCoords);