Add bitmapmesh, rect, and patch rendering

bug:22480459

Change-Id: Id9e9146997dd018b3e4e785c2bc13689e3cf7c3c
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 3d35dd5..f1c89b895 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -20,6 +20,7 @@
 #include "Caches.h"
 #include "Glop.h"
 #include "GlopBuilder.h"
+#include "Patch.h"
 #include "PathTessellator.h"
 #include "renderstate/OffscreenBufferPool.h"
 #include "renderstate/RenderState.h"
@@ -56,8 +57,6 @@
     if (entry) {
         entry->uvMapper.map(texCoords);
     }
-    // init to non-empty, so we can safely expandtoCoverRect
-    Rect totalBounds = firstState.computedState.clippedBounds;
     for (size_t i = 0; i < opList.count; i++) {
         const BakedOpState& state = *(opList.states[i]);
         TextureVertex* rectVerts = &vertices[i * 4];
@@ -68,8 +67,6 @@
         }
         storeTexturedRect(rectVerts, opBounds, texCoords);
         renderer.dirtyRenderTarget(opBounds);
-
-        totalBounds.expandToCover(opBounds);
     }
 
     const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
@@ -80,7 +77,111 @@
             .setMeshTexturedIndexedQuads(vertices, opList.count * 6)
             .setFillTexturePaint(*texture, textureFillFlags, firstState.op->paint, firstState.alpha)
             .setTransform(Matrix4::identity(), TransformFlags::None)
-            .setModelViewOffsetRect(0, 0, totalBounds) // don't snap here, we snap per-quad above
+            .setModelViewIdentityEmptyBounds()
+            .build();
+    renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
+}
+
+void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
+        const MergedBakedOpList& opList) {
+    const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op));
+    const BakedOpState& firstState = *(opList.states[0]);
+    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(
+            firstOp.bitmap->pixelRef());
+
+    // Batches will usually contain a small number of items so it's
+    // worth performing a first iteration to count the exact number
+    // of vertices we need in the new mesh
+    uint32_t totalVertices = 0;
+
+    for (size_t i = 0; i < opList.count; i++) {
+        const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
+
+        // TODO: cache mesh lookups
+        const Patch* opMesh = renderer.caches().patchCache.get(
+                entry, op.bitmap->width(), op.bitmap->height(),
+                op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+        totalVertices += opMesh->verticesCount;
+    }
+
+    const bool dirtyRenderTarget = renderer.offscreenRenderTarget();
+
+    uint32_t indexCount = 0;
+
+    TextureVertex vertices[totalVertices];
+    TextureVertex* vertex = &vertices[0];
+    // Create a mesh that contains the transformed vertices for all the
+    // 9-patch objects that are part of the batch. Note that onDefer()
+    // enforces ops drawn by this function to have a pure translate or
+    // identity matrix
+    for (size_t i = 0; i < opList.count; i++) {
+        const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
+        const BakedOpState& state = *opList.states[i];
+
+        // TODO: cache mesh lookups
+        const Patch* opMesh = renderer.caches().patchCache.get(
+                entry, op.bitmap->width(), op.bitmap->height(),
+                op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+
+
+        uint32_t vertexCount = opMesh->verticesCount;
+        if (vertexCount == 0) continue;
+
+        // We use the bounds to know where to translate our vertices
+        // Using patchOp->state.mBounds wouldn't work because these
+        // bounds are clipped
+        const float tx = floorf(state.computedState.transform.getTranslateX()
+                + op.unmappedBounds.left + 0.5f);
+        const float ty = floorf(state.computedState.transform.getTranslateY()
+                + op.unmappedBounds.top + 0.5f);
+
+        // Copy & transform all the vertices for the current operation
+        TextureVertex* opVertices = opMesh->vertices.get();
+        for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {
+            TextureVertex::set(vertex++,
+                    opVertices->x + tx, opVertices->y + ty,
+                    opVertices->u, opVertices->v);
+        }
+
+        // Dirty the current layer if possible. When the 9-patch does not
+        // contain empty quads we can take a shortcut and simply set the
+        // dirty rect to the object's bounds.
+        if (dirtyRenderTarget) {
+            if (!opMesh->hasEmptyQuads) {
+                renderer.dirtyRenderTarget(Rect(tx, ty,
+                        tx + op.unmappedBounds.getWidth(), ty + op.unmappedBounds.getHeight()));
+            } else {
+                const size_t count = opMesh->quads.size();
+                for (size_t i = 0; i < count; i++) {
+                    const Rect& quadBounds = opMesh->quads[i];
+                    const float x = tx + quadBounds.left;
+                    const float y = ty + quadBounds.top;
+                    renderer.dirtyRenderTarget(Rect(x, y,
+                            x + quadBounds.getWidth(), y + quadBounds.getHeight()));
+                }
+            }
+        }
+
+        indexCount += opMesh->indexCount;
+    }
+
+
+    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    // 9 patches are built for stretching - always filter
+    int textureFillFlags = TextureFillFlags::ForceFilter;
+    if (firstOp.bitmap->colorType() == kAlpha_8_SkColorType) {
+        textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
+    }
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(firstState.roundRectClipState)
+            .setMeshTexturedIndexedQuads(vertices, indexCount)
+            .setFillTexturePaint(*texture, textureFillFlags, firstOp.paint, firstState.alpha)
+            .setTransform(Matrix4::identity(), TransformFlags::None)
+            .setModelViewIdentityEmptyBounds()
             .build();
     renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
 }
@@ -310,6 +411,105 @@
     renderer.renderGlop(state, glop);
 }
 
+void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) {
+    const static UvMapper defaultUvMapper;
+    const uint32_t elementCount = op.meshWidth * op.meshHeight * 6;
+
+    std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]);
+    ColorTextureVertex* vertex = &mesh[0];
+
+    const int* colors = op.colors;
+    std::unique_ptr<int[]> tempColors;
+    if (!colors) {
+        uint32_t colorsCount = (op.meshWidth + 1) * (op.meshHeight + 1);
+        tempColors.reset(new int[colorsCount]);
+        memset(tempColors.get(), 0xff, colorsCount * sizeof(int));
+        colors = tempColors.get();
+    }
+
+    Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef());
+    const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper);
+
+    for (int32_t y = 0; y < op.meshHeight; y++) {
+        for (int32_t x = 0; x < op.meshWidth; x++) {
+            uint32_t i = (y * (op.meshWidth + 1) + x) * 2;
+
+            float u1 = float(x) / op.meshWidth;
+            float u2 = float(x + 1) / op.meshWidth;
+            float v1 = float(y) / op.meshHeight;
+            float v2 = float(y + 1) / op.meshHeight;
+
+            mapper.map(u1, v1, u2, v2);
+
+            int ax = i + (op.meshWidth + 1) * 2;
+            int ay = ax + 1;
+            int bx = i;
+            int by = bx + 1;
+            int cx = i + 2;
+            int cy = cx + 1;
+            int dx = i + (op.meshWidth + 1) * 2 + 2;
+            int dy = dx + 1;
+
+            const float* vertices = op.vertices;
+            ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
+            ColorTextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2, colors[ax / 2]);
+            ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
+
+            ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
+            ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
+            ColorTextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1, colors[cx / 2]);
+        }
+    }
+
+    if (!texture) {
+        texture = renderer.caches().textureCache.get(op.bitmap);
+        if (!texture) {
+            return;
+        }
+    }
+    const AutoTexture autoCleanup(texture);
+
+    /*
+     * TODO: handle alpha_8 textures correctly by applying paint color, but *not*
+     * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh.
+     */
+    const int textureFillFlags = TextureFillFlags::None;
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshColoredTexturedMesh(mesh.get(), elementCount)
+            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+            .setTransform(state.computedState.transform,  TransformFlags::None)
+            .setModelViewOffsetRect(0, 0, op.unmappedBounds)
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op, const BakedOpState& state) {
+    Texture* texture = renderer.getTexture(op.bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    Rect uv(std::max(0.0f, op.src.left / texture->width),
+            std::max(0.0f, op.src.top / texture->height),
+            std::min(1.0f, op.src.right / texture->width),
+            std::min(1.0f, op.src.bottom / texture->height));
+
+    const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
+            ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+    const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth())
+            && MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight());
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshTexturedUvQuad(texture->uvMapper, uv)
+            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
 void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
     VertexBuffer buffer;
     PathTessellator::tessellateLines(op.points, op.floatCount, op.paint,
@@ -334,6 +534,35 @@
     }
 }
 
+void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, const BakedOpState& state) {
+    // 9 patches are built for stretching - always filter
+    int textureFillFlags = TextureFillFlags::ForceFilter;
+    if (op.bitmap->colorType() == kAlpha_8_SkColorType) {
+        textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
+    }
+
+    // TODO: avoid redoing the below work each frame:
+    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef());
+    const Patch* mesh = renderer.caches().patchCache.get(
+            entry, op.bitmap->width(), op.bitmap->height(),
+            op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+
+    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshPatchQuads(*mesh)
+            .setMeshTexturedUnitQuad(texture->uvMapper)
+            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+                    Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
 void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, const BakedOpState& state) {
     PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint);
     const AutoTexture holder(texture);
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 9c8649f..8acdb62 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -73,7 +73,7 @@
             .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
             .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
             .setTransform(bakedState->computedState.transform, transformFlags)
-            .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+            .setModelViewIdentityEmptyBounds()
             .build();
     // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer
     renderer->renderGlop(nullptr, clip, glop);
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 4785ea4..bcf819e 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -64,7 +64,7 @@
 
         // Canvas transform isn't applied to the mesh at draw time,
         //since it's already built in.
-        MeshIgnoresCanvasTransform = 1 << 1,
+        MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove
     };
 };
 
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index b647b90..6e5797d 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -95,6 +95,10 @@
             return setModelViewOffsetRect(offsetX, offsetY, source);
         }
     }
+    GlopBuilder& setModelViewIdentityEmptyBounds() {
+        // pass empty rect since not needed for damage / snap
+        return setModelViewOffsetRect(0, 0, Rect());
+    }
 
     GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
 
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index f948f18..b936e6d5 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -757,6 +757,18 @@
     }
 }
 
+void OpReorderer::onBitmapMeshOp(const BitmapMeshOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+}
+
+void OpReorderer::onBitmapRectOp(const BitmapRectOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+}
+
 void OpReorderer::onLinesOp(const LinesOp& op) {
     batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
     onStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
@@ -766,6 +778,23 @@
     onStrokeableOp(op, tessBatchId(op));
 }
 
+void OpReorderer::onPatchOp(const PatchOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+
+    if (bakedState->computedState.transform.isPureTranslate()
+            && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+        mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
+        // TODO: AssetAtlas in mergeId
+
+        // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
+        currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
+    } else {
+        // Use Bitmap batchId since Bitmap+Patch use same shader
+        currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+    }
+}
+
 void OpReorderer::onPathOp(const PathOp& op) {
     onStrokeableOp(op, OpBatchType::Bitmap);
 }
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 58b607c..0b88f04 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -45,7 +45,7 @@
     enum {
         None = 0, // Don't batch
         Bitmap,
-        Patch,
+        MergedPatch,
         AlphaVertices,
         Vertices,
         AlphaMaskTexture,
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 8ce5473..75ecdae 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -24,7 +24,8 @@
 #include "utils/LinearAllocator.h"
 #include "Vector.h"
 
-#include "SkXfermode.h"
+#include <androidfw/ResourceTypes.h>
+#include <SkXfermode.h>
 
 class SkBitmap;
 class SkPaint;
@@ -45,8 +46,11 @@
 #define MAP_OPS_BASED_ON_MERGEABILITY(U_OP_FN, M_OP_FN) \
         U_OP_FN(ArcOp) \
         M_OP_FN(BitmapOp) \
+        U_OP_FN(BitmapMeshOp) \
+        U_OP_FN(BitmapRectOp) \
         U_OP_FN(LinesOp) \
         U_OP_FN(OvalOp) \
+        M_OP_FN(PatchOp) \
         U_OP_FN(PathOp) \
         U_OP_FN(PointsOp) \
         U_OP_FN(RectOp) \
@@ -152,6 +156,31 @@
     // TODO: asset atlas/texture id lookup?
 };
 
+struct BitmapMeshOp : RecordedOp {
+    BitmapMeshOp(BASE_PARAMS, const SkBitmap* bitmap, int meshWidth, int meshHeight,
+            const float* vertices, const int* colors)
+            : SUPER(BitmapMeshOp)
+            , bitmap(bitmap)
+            , meshWidth(meshWidth)
+            , meshHeight(meshHeight)
+            , vertices(vertices)
+            , colors(colors) {}
+    const SkBitmap* bitmap;
+    const int meshWidth;
+    const int meshHeight;
+    const float* vertices;
+    const int* colors;
+};
+
+struct BitmapRectOp : RecordedOp {
+    BitmapRectOp(BASE_PARAMS, const SkBitmap* bitmap, const Rect& src)
+            : SUPER(BitmapRectOp)
+            , bitmap(bitmap)
+            , src(src) {}
+    const SkBitmap* bitmap;
+    const Rect src;
+};
+
 struct LinesOp : RecordedOp {
     LinesOp(BASE_PARAMS, const float* points, const int floatCount)
             : SUPER(LinesOp)
@@ -166,6 +195,16 @@
             : SUPER(OvalOp) {}
 };
 
+
+struct PatchOp : RecordedOp {
+    PatchOp(BASE_PARAMS, const SkBitmap* bitmap, const Res_png_9patch* patch)
+            : SUPER(PatchOp)
+            , bitmap(bitmap)
+            , patch(patch) {}
+    const SkBitmap* bitmap;
+    const Res_png_9patch* patch;
+};
+
 struct PathOp : RecordedOp {
     PathOp(BASE_PARAMS, const SkPath* path)
             : SUPER(PathOp)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 148c940..57f0d34 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -418,19 +418,35 @@
         drawBitmap(&bitmap, paint);
         restore();
     } else {
-        LOG_ALWAYS_FATAL("TODO!");
+        addOp(new (alloc()) BitmapRectOp(
+                Rect(dstLeft, dstTop, dstRight, dstBottom),
+                *(mState.currentSnapshot()->transform),
+                mState.getRenderTargetClipBounds(),
+                refPaint(paint), refBitmap(bitmap),
+                Rect(srcLeft, srcTop, srcRight, srcBottom)));
     }
 }
 
 void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
             const float* vertices, const int* colors, const SkPaint* paint) {
-    LOG_ALWAYS_FATAL("TODO!");
+    int vertexCount = (meshWidth + 1) * (meshHeight + 1);
+    addOp(new (alloc()) BitmapMeshOp(
+            calcBoundsOfPoints(vertices, vertexCount * 2),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
+            refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
+            refBuffer<int>(colors, vertexCount))); // 1 color per vertex
 }
 
-void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch,
             float dstLeft, float dstTop, float dstRight, float dstBottom,
             const SkPaint* paint) {
-    LOG_ALWAYS_FATAL("TODO!");
+    addOp(new (alloc()) PatchOp(
+            Rect(dstLeft, dstTop, dstRight, dstBottom),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
 }
 
 // Text
@@ -452,7 +468,6 @@
 
 void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) {
-    // NOTE: can't use refPaint() directly, since it forces left alignment
     LOG_ALWAYS_FATAL("TODO!");
 }