diff options
-rw-r--r-- | libs/hwui/BakedOpDispatcher.cpp | 239 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/Glop.h | 2 | ||||
-rw-r--r-- | libs/hwui/GlopBuilder.h | 4 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.cpp | 29 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.h | 2 | ||||
-rw-r--r-- | libs/hwui/RecordedOp.h | 41 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 25 |
8 files changed, 330 insertions, 14 deletions
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 3d35dd5b1fe8..f1c89b895048 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 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, 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 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, } storeTexturedRect(rectVerts, opBounds, texCoords); renderer.dirtyRenderTarget(opBounds); - - totalBounds.expandToCover(opBounds); } const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) @@ -80,7 +77,111 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, .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 @@ void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op 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::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, co } } +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 9c8649fc9775..8acdb624a1ce 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -73,7 +73,7 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { .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 4785ea48cddc..bcf819eb42e0 100644 --- a/libs/hwui/Glop.h +++ b/libs/hwui/Glop.h @@ -64,7 +64,7 @@ namespace TransformFlags { // 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 b647b90a62e0..6e5797d1434b 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -95,6 +95,10 @@ public: 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 f948f187451d..b936e6d5b062 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -757,6 +757,18 @@ void OpReorderer::onBitmapOp(const BitmapOp& op) { } } +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 @@ void OpReorderer::onOvalOp(const OvalOp& op) { 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 58b607cb9182..0b88f049b583 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -45,7 +45,7 @@ namespace OpBatchType { 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 8ce5473aeb4f..75ecdae3aa6f 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 @@ struct Vertex; #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 @@ struct BitmapOp : RecordedOp { // 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 @@ struct OvalOp : RecordedOp { : 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 148c94083047..57f0d349172c 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -418,19 +418,35 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float sr 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::drawText(const uint16_t* glyphs, const float* positions, i 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!"); } |