diff options
25 files changed, 425 insertions, 196 deletions
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp index 7e0969916825..41411a98a4bf 100644 --- a/libs/hwui/AssetAtlas.cpp +++ b/libs/hwui/AssetAtlas.cpp @@ -79,13 +79,13 @@ void AssetAtlas::updateTextureId() { // Entries /////////////////////////////////////////////////////////////////////////////// -AssetAtlas::Entry* AssetAtlas::getEntry(const SkBitmap* bitmap) const { - ssize_t index = mEntries.indexOfKey(bitmap->pixelRef()); +AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const { + ssize_t index = mEntries.indexOfKey(pixelRef); return index >= 0 ? mEntries.valueAt(index) : nullptr; } -Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const { - ssize_t index = mEntries.indexOfKey(bitmap->pixelRef()); +Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const { + ssize_t index = mEntries.indexOfKey(pixelRef); return index >= 0 ? mEntries.valueAt(index)->texture : nullptr; } diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h index f1cd0b4947dc..a037725b1c6c 100644 --- a/libs/hwui/AssetAtlas.h +++ b/libs/hwui/AssetAtlas.h @@ -148,15 +148,15 @@ public: /** * Returns the entry in the atlas associated with the specified - * bitmap. If the bitmap is not in the atlas, return NULL. + * pixelRef. If the pixelRef is not in the atlas, return NULL. */ - Entry* getEntry(const SkBitmap* bitmap) const; + Entry* getEntry(const SkPixelRef* pixelRef) const; /** * Returns the texture for the atlas entry associated with the - * specified bitmap. If the bitmap is not in the atlas, return NULL. + * specified pixelRef. If the pixelRef is not in the atlas, return NULL. */ - Texture* getEntryTexture(const SkBitmap* bitmap) const; + Texture* getEntryTexture(const SkPixelRef* pixelRef) const; private: void createEntries(Caches& caches, int64_t* map, int count); diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index b56b1e4c5f93..fde12dd261f5 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -31,20 +31,182 @@ namespace android { namespace uirenderer { +static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) { + vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top }; + vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top }; + vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom }; + vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom }; +} + +void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, + const MergedBakedOpList& opList) { + + const BakedOpState& firstState = *(opList.states[0]); + const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap; + + AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef()); + Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + + TextureVertex vertices[opList.count * 4]; + Rect texCoords(0, 0, 1, 1); + 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]; + Rect opBounds = state.computedState.clippedBounds; + if (CC_LIKELY(state.computedState.transform.isPureTranslate())) { + // pure translate, so snap (same behavior as onBitmapOp) + opBounds.snapToPixelBoundaries(); + } + storeTexturedRect(rectVerts, opBounds, texCoords); + renderer.dirtyRenderTarget(opBounds); + + totalBounds.expandToCover(opBounds); + } + + const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) + ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(firstState.roundRectClipState) + .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 + .build(); + renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop); +} + +static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer, + const TextOp& op, const BakedOpState& state) { + renderer.caches().textureState().activateTexture(0); + + PaintUtils::TextShadow textShadow; + if (!PaintUtils::getTextShadow(op.paint, &textShadow)) { + LOG_ALWAYS_FATAL("failed to query shadow attributes"); + } + + renderer.caches().dropShadowCache.setFontRenderer(fontRenderer); + ShadowTexture* texture = renderer.caches().dropShadowCache.get( + op.paint, (const char*) op.glyphs, + op.glyphCount, textShadow.radius, op.positions); + // If the drop shadow exceeds the max texture size or couldn't be + // allocated, skip drawing + if (!texture) return; + const AutoTexture autoCleanup(texture); + + const float sx = op.x - texture->left + textShadow.dx; + const float sy = op.y - texture->top + textShadow.dy; + + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height)) + .build(); + renderer.renderGlop(state, glop); +} + +enum class TextRenderType { + Defer, + Flush +}; + +static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state, + const Rect* renderClip, TextRenderType renderType) { + FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); + + if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) { + fontRenderer.setFont(op.paint, SkMatrix::I()); + renderTextShadow(renderer, fontRenderer, op, state); + } + + float x = op.x; + float y = op.y; + const Matrix4& transform = state.computedState.transform; + const bool pureTranslate = transform.isPureTranslate(); + if (CC_LIKELY(pureTranslate)) { + x = floorf(x + transform.getTranslateX() + 0.5f); + y = floorf(y + transform.getTranslateY() + 0.5f); + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(false); + } else if (CC_UNLIKELY(transform.isPerspective())) { + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(true); + } else { + // We only pass a partial transform to the font renderer. That partial + // matrix defines how glyphs are rasterized. Typically we want glyphs + // to be rasterized at their final size on screen, which means the partial + // matrix needs to take the scale factor into account. + // When a partial matrix is used to transform glyphs during rasterization, + // the mesh is generated with the inverse transform (in the case of scale, + // the mesh is generated at 1.0 / scale for instance.) This allows us to + // apply the full transform matrix at draw time in the vertex shader. + // Applying the full matrix in the shader is the easiest way to handle + // rotation and perspective and allows us to always generated quads in the + // font renderer which greatly simplifies the code, clipping in particular. + float sx, sy; + transform.decomposeScale(sx, sy); + fontRenderer.setFont(op.paint, SkMatrix::MakeScale( + roundf(std::max(1.0f, sx)), + roundf(std::max(1.0f, sy)))); + fontRenderer.setTextureFiltering(true); + } + Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); + + int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); + TextDrawFunctor functor(&renderer, &state, renderClip, + x, y, pureTranslate, alpha, mode, op.paint); + + bool forceFinish = (renderType == TextRenderType::Flush); + bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); + const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect : nullptr; + fontRenderer.renderPosText(op.paint, localOpClip, + (const char*) op.glyphs, op.glyphCount, x, y, + op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish); + + if (mustDirtyRenderTarget) { + if (!pureTranslate) { + transform.mapRect(layerBounds); + } + renderer.dirtyRenderTarget(layerBounds); + } +} + +void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer, + const MergedBakedOpList& opList) { + const Rect* clip = opList.clipSideFlags ? &opList.clip : nullptr; + for (size_t i = 0; i < opList.count; i++) { + const BakedOpState& state = *(opList.states[i]); + const TextOp& op = *(static_cast<const TextOp*>(state.op)); + TextRenderType renderType = (i + 1 == opList.count) + ? TextRenderType::Flush : TextRenderType::Defer; + renderTextOp(renderer, op, state, clip, renderType); + } +} + void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) { LOG_ALWAYS_FATAL("unsupported operation"); } -void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) { +void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer&, const BeginLayerOp&, const BakedOpState&) { LOG_ALWAYS_FATAL("unsupported operation"); } -void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) { +void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer&, const EndLayerOp&, const BakedOpState&) { LOG_ALWAYS_FATAL("unsupported operation"); } void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) { - renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere? Texture* texture = renderer.getTexture(op.bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -153,89 +315,9 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR renderer.renderGlop(state, glop); } -static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer, - const TextOp& op, const BakedOpState& state) { - renderer.caches().textureState().activateTexture(0); - - PaintUtils::TextShadow textShadow; - if (!PaintUtils::getTextShadow(op.paint, &textShadow)) { - LOG_ALWAYS_FATAL("failed to query shadow attributes"); - } - - renderer.caches().dropShadowCache.setFontRenderer(fontRenderer); - ShadowTexture* texture = renderer.caches().dropShadowCache.get( - op.paint, (const char*) op.glyphs, - op.glyphCount, textShadow.radius, op.positions); - // If the drop shadow exceeds the max texture size or couldn't be - // allocated, skip drawing - if (!texture) return; - const AutoTexture autoCleanup(texture); - - const float sx = op.x - texture->left + textShadow.dx; - const float sy = op.y - texture->top + textShadow.dy; - - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshTexturedUnitQuad(nullptr) - .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height)) - .build(); - renderer.renderGlop(state, glop); -} - void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) { - FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); - - if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) { - fontRenderer.setFont(op.paint, SkMatrix::I()); - renderTextShadow(renderer, fontRenderer, op, state); - } - - float x = op.x; - float y = op.y; - const Matrix4& transform = state.computedState.transform; - const bool pureTranslate = transform.isPureTranslate(); - if (CC_LIKELY(pureTranslate)) { - x = floorf(x + transform.getTranslateX() + 0.5f); - y = floorf(y + transform.getTranslateY() + 0.5f); - fontRenderer.setFont(op.paint, SkMatrix::I()); - fontRenderer.setTextureFiltering(false); - } else if (CC_UNLIKELY(transform.isPerspective())) { - fontRenderer.setFont(op.paint, SkMatrix::I()); - fontRenderer.setTextureFiltering(true); - } else { - // We only pass a partial transform to the font renderer. That partial - // matrix defines how glyphs are rasterized. Typically we want glyphs - // to be rasterized at their final size on screen, which means the partial - // matrix needs to take the scale factor into account. - // When a partial matrix is used to transform glyphs during rasterization, - // the mesh is generated with the inverse transform (in the case of scale, - // the mesh is generated at 1.0 / scale for instance.) This allows us to - // apply the full transform matrix at draw time in the vertex shader. - // Applying the full matrix in the shader is the easiest way to handle - // rotation and perspective and allows us to always generated quads in the - // font renderer which greatly simplifies the code, clipping in particular. - float sx, sy; - transform.decomposeScale(sx, sy); - fontRenderer.setFont(op.paint, SkMatrix::MakeScale( - roundf(std::max(1.0f, sx)), - roundf(std::max(1.0f, sy)))); - fontRenderer.setTextureFiltering(true); - } - - // TODO: Implement better clipping for scaled/rotated text - const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect; - Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); - - int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha; - SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); - TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint); - - bool hasActiveLayer = false; // TODO - fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y, - op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging + const Rect* clip = state.computedState.clipSideFlags ? &state.computedState.clipRect : nullptr; + renderTextOp(renderer, op, state, clip, TextRenderType::Flush); } void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h index caf14bfeef6d..0e763d9c660b 100644 --- a/libs/hwui/BakedOpDispatcher.h +++ b/libs/hwui/BakedOpDispatcher.h @@ -26,16 +26,21 @@ namespace uirenderer { /** * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer. - * - * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is - * minimal through public BakedOpRenderer APIs. */ class BakedOpDispatcher { public: + // Declares all "onMergedBitmapOps(...)" style methods for mergeable op types +#define X(Type) \ + static void onMerged##Type##s(BakedOpRenderer& renderer, const MergedBakedOpList& opList); + MAP_MERGED_OPS(X) +#undef X + // Declares all "onBitmapOp(...)" style methods for every op type -#define DISPATCH_METHOD(Type) \ +#define X(Type) \ static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state); - MAP_OPS(DISPATCH_METHOD); + MAP_OPS(X) +#undef X + }; }; // namespace uirenderer diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 6cdc320a0bad..93a9406da7bc 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -121,30 +121,35 @@ void BakedOpRenderer::clearColorBuffer(const Rect& rect) { } Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) { - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); if (!texture) { return mCaches.textureCache.get(bitmap); } return texture; } -void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) { - bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None; - mRenderState.scissor().setEnabled(useScissor); - if (useScissor) { - const Rect& clip = state.computedState.clipRect; - mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom, - clip.getWidth(), clip.getHeight()); +void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop) { + mRenderState.scissor().setEnabled(clip != nullptr); + if (clip) { + mRenderState.scissor().set(clip->left, mRenderTarget.viewportHeight - clip->bottom, + clip->getWidth(), clip->getHeight()); } - if (mRenderTarget.offscreenBuffer) { // TODO: not with multi-draw + if (dirtyBounds && mRenderTarget.offscreenBuffer) { // register layer damage to draw-back region - const Rect& uiDirty = state.computedState.clippedBounds; - android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom); + android::Rect dirty(dirtyBounds->left, dirtyBounds->top, + dirtyBounds->right, dirtyBounds->bottom); mRenderTarget.offscreenBuffer->region.orSelf(dirty); } mRenderState.render(glop, mRenderTarget.orthoMatrix); if (!mRenderTarget.frameBufferId) mHasDrawn = true; } +void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) { + if (mRenderTarget.offscreenBuffer) { + android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom); + mRenderTarget.offscreenBuffer->region.orSelf(dirty); + } +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index 62d183846e27..d7600db0e255 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -67,7 +67,16 @@ public: Texture* getTexture(const SkBitmap* bitmap); const LightInfo& getLightInfo() { return mLightInfo; } - void renderGlop(const BakedOpState& state, const Glop& glop); + void renderGlop(const BakedOpState& state, const Glop& glop) { + bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None; + renderGlop(&state.computedState.clippedBounds, + useScissor ? &state.computedState.clipRect : nullptr, + glop); + } + + void renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop); + bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; } + void dirtyRenderTarget(const Rect& dirtyRect); bool didDraw() { return mHasDrawn; } private: void setViewport(uint32_t width, uint32_t height); diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index 9a40c3b3d3b0..983c27b4a511 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -38,6 +38,16 @@ namespace OpClipSideFlags { } /** + * Holds a list of BakedOpStates of ops that can be drawn together + */ +struct MergedBakedOpList { + const BakedOpState*const* states; + size_t count; + int clipSideFlags; + Rect clip; +}; + +/** * Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot */ class ResolvedRenderState { diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index a9d1e4284d2e..fd6f0b5739d2 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -26,7 +26,7 @@ namespace uirenderer { static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { Vertex v = {x, y}; transform.mapPoint(v.x, v.y); - transformedBounds.expandToCoverVertex(v.x, v.y); + transformedBounds.expandToCover(v.x, v.y); } Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index e7cc464facd8..92217edc2f16 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -612,7 +612,7 @@ public: AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) { if (!mEntryValid) { mEntryValid = true; - mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap); + mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef()); } return mEntry; } @@ -777,7 +777,7 @@ public: AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) { if (!mEntryValid) { mEntryValid = true; - mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap); + mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef()); } return mEntry; } diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 47654f400ec2..9c8649fc9775 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -75,7 +75,8 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { .setTransform(bakedState->computedState.transform, transformFlags) .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0)) .build(); - renderer->renderGlop(*bakedState, glop); + // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer + renderer->renderGlop(nullptr, clip, glop); #else GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop) .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState) diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 87cfe7ff98e0..ff4dc4aef94a 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -57,6 +57,7 @@ public: #if HWUI_NEW_OPS BakedOpRenderer* renderer, const BakedOpState* bakedState, + const Rect* clip, #else OpenGLRenderer* renderer, #endif @@ -65,6 +66,7 @@ public: : renderer(renderer) #if HWUI_NEW_OPS , bakedState(bakedState) + , clip(clip) #endif , x(x) , y(y) @@ -79,6 +81,7 @@ public: #if HWUI_NEW_OPS BakedOpRenderer* renderer; const BakedOpState* bakedState; + const Rect* clip; #else OpenGLRenderer* renderer; #endif diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 6270dcbe7a84..b647b90a62e0 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -53,7 +53,7 @@ public: GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs); GlopBuilder& setMeshVertexBuffer(const VertexBuffer& vertexBuffer, bool shadowInterp); GlopBuilder& setMeshIndexedQuads(Vertex* vertexData, int quadCount); - GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: use indexed quads + GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: delete GlopBuilder& setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, int elementCount); // TODO: use indexed quads GlopBuilder& setMeshTexturedIndexedQuads(TextureVertex* vertexData, int elementCount); // TODO: take quadCount GlopBuilder& setMeshPatchQuads(const Patch& patch); diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index 9cbd9c2d9ffc..9460361f9451 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -202,6 +202,9 @@ public: if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom; } + bool getClipSideFlags() const { return mClipSideFlags; } + const Rect& getClipRect() const { return mClipRect; } + private: int mClipSideFlags = 0; Rect mClipRect; @@ -291,12 +294,31 @@ void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator, } } -void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const { +void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, + BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const { ATRACE_NAME("flush drawing commands"); for (const BatchBase* batch : mBatches) { - // TODO: different behavior based on batch->isMerging() - for (const BakedOpState* op : batch->getOps()) { - receivers[op->op->opId](arg, *op->op, *op); + size_t size = batch->getOps().size(); + if (size > 1 && batch->isMerging()) { + int opId = batch->getOps()[0]->op->opId; + const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch); + MergedBakedOpList data = { + batch->getOps().data(), + size, + mergingBatch->getClipSideFlags(), + mergingBatch->getClipRect() + }; + if (data.clipSideFlags) { + // if right or bottom sides aren't used to clip, init them to viewport bounds + // in the clip rect, so it can be used to scissor + if (!(data.clipSideFlags & OpClipSideFlags::Right)) data.clip.right = width; + if (!(data.clipSideFlags & OpClipSideFlags::Bottom)) data.clip.bottom = height; + } + mergedReceivers[opId](arg, data); + } else { + for (const BakedOpState* op : batch->getOps()) { + unmergedReceivers[op->op->opId](arg, *op); + } } } } @@ -639,7 +661,8 @@ void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) { #define OP_RECEIVER(Type) \ [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); }, void OpReorderer::deferNodeOps(const RenderNode& renderNode) { - static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = { + typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op); + static OpDispatcher receivers[] = { MAP_OPS(OP_RECEIVER) }; @@ -692,42 +715,57 @@ static batchid_t tessellatedBatchId(const SkPaint& paint) { } void OpReorderer::onBitmapOp(const BitmapOp& op) { - BakedOpState* bakedStateOp = tryBakeOpState(op); - if (!bakedStateOp) return; // quick rejected - - mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID(); - // TODO: AssetAtlas - currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId); + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + + // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation + // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in + // MergingDrawBatch::canMergeWith() + if (bakedState->computedState.transform.isSimple() + && bakedState->computedState.transform.positiveScale() + && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode + && op.bitmap->colorType() != kAlpha_8_SkColorType) { + mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID(); + // TODO: AssetAtlas in mergeId + currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId); + } else { + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); + } } void OpReorderer::onLinesOp(const LinesOp& op) { - BakedOpState* bakedStateOp = tryBakeOpState(op); - if (!bakedStateOp) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); - + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint)); } void OpReorderer::onRectOp(const RectOp& op) { - BakedOpState* bakedStateOp = tryBakeOpState(op); - if (!bakedStateOp) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint)); + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint)); } void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { - BakedOpState* bakedStateOp = tryBakeOpState(op); - if (!bakedStateOp) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); } void OpReorderer::onTextOp(const TextOp& op) { - BakedOpState* bakedStateOp = tryBakeOpState(op); - if (!bakedStateOp) return; // quick rejected + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected // TODO: better handling of shader (since we won't care about color then) batchid_t batchId = op.paint->getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText; - mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor()); - currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId); + + if (bakedState->computedState.transform.isPureTranslate() + && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) { + mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor()); + currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId); + } else { + currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); + } } void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index 00df8b0951e6..fc77c617fd89 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -58,7 +58,8 @@ namespace OpBatchType { } class OpReorderer : public CanvasStateClient { - typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpDispatcher; + typedef void (*BakedOpReceiver)(void*, const BakedOpState&); + typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList); /** * Stores the deferred render operations and state used to compute ordering @@ -87,7 +88,7 @@ class OpReorderer : public CanvasStateClient { void deferMergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId, mergeid_t mergeId); - void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const; + void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const; bool empty() const { return mBatches.empty(); @@ -132,19 +133,44 @@ public: * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use * state->op->opId to lookup a receiver that will be called when the op is replayed. * - * For example a BitmapOp would resolve, via the lambda lookup, to calling: - * - * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state); */ -#define BAKED_OP_RECEIVER(Type) \ - [](void* internalRenderer, const RecordedOp& op, const BakedOpState& state) { \ - StaticDispatcher::on##Type(*(static_cast<Renderer*>(internalRenderer)), static_cast<const Type&>(op), state); \ - }, template <typename StaticDispatcher, typename Renderer> void replayBakedOps(Renderer& renderer) { - static BakedOpDispatcher receivers[] = { - MAP_OPS(BAKED_OP_RECEIVER) + /** + * defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to + * dispatch the op via a method on a static dispatcher when the op is replayed. + * + * For example a BitmapOp would resolve, via the lambda lookup, to calling: + * + * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state); + */ + #define X(Type) \ + [](void* renderer, const BakedOpState& state) { \ + StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), static_cast<const Type&>(*(state.op)), state); \ + }, + static BakedOpReceiver unmergedReceivers[] = { + MAP_OPS(X) + }; + #undef X + + /** + * defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a + * static dispatcher when the group of merged ops is replayed. Unmergeable ops trigger + * a LOG_ALWAYS_FATAL(). + */ + #define X(Type) \ + [](void* renderer, const MergedBakedOpList& opList) { \ + LOG_ALWAYS_FATAL("op type %d does not support merging", opList.states[0]->op->opId); \ + }, + #define Y(Type) \ + [](void* renderer, const MergedBakedOpList& opList) { \ + StaticDispatcher::onMerged##Type##s(*(static_cast<Renderer*>(renderer)), opList); \ + }, + static MergedOpReceiver mergedReceivers[] = { + MAP_OPS_BASED_ON_MERGEABILITY(X, Y) }; + #undef X + #undef Y // Relay through layers in reverse order, since layers // later in the list will be drawn by earlier ones @@ -153,18 +179,18 @@ public: if (layer.renderNode) { // cached HW layer - can't skip layer if empty renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect); - layer.replayBakedOpsImpl((void*)&renderer, receivers); + layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); renderer.endLayer(); } else if (!layer.empty()) { // save layer - skip entire layer if empty layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height); - layer.replayBakedOpsImpl((void*)&renderer, receivers); + layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); renderer.endLayer(); } } const LayerReorderer& fbo0 = mLayerReorderers[0]; renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect); - fbo0.replayBakedOpsImpl((void*)&renderer, receivers); + fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); renderer.endFrame(); } @@ -213,7 +239,7 @@ private: void deferRenderNodeOp(const RenderNodeOp& op); - void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers); + void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers); SkPath* createFrameAllocatedPath() { mFrameAllocatedPaths.emplace_back(new SkPath); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index e386b1cacf74..2cb32c404525 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1525,7 +1525,7 @@ void OpenGLRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int m colors = tempColors.get(); } - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); const UvMapper& mapper(getMapper(texture)); for (int32_t y = 0; y < meshHeight; y++) { @@ -2146,7 +2146,7 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float bool status; #if HWUI_NEW_OPS LOG_ALWAYS_FATAL("unsupported"); - TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint); + TextDrawFunctor functor(nullptr, nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint); #else TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); #endif @@ -2190,7 +2190,7 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); #if HWUI_NEW_OPS LOG_ALWAYS_FATAL("unsupported"); - TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint); + TextDrawFunctor functor(nullptr, nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint); #else TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); #endif @@ -2308,7 +2308,7 @@ void OpenGLRenderer::setDrawFilter(SkDrawFilter* filter) { /////////////////////////////////////////////////////////////////////////////// Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) { - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); if (!texture) { return mCaches.textureCache.get(bitmap); } diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp index b57b8f04d1de..9246237aeffb 100644 --- a/libs/hwui/PathTessellator.cpp +++ b/libs/hwui/PathTessellator.cpp @@ -799,7 +799,7 @@ static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer, dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2); for (int i = 0; i < count; i += 2) { - bounds.expandToCoverVertex(points[i + 0], points[i + 1]); + bounds.expandToCover(points[i + 0], points[i + 1]); dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]); } dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint); @@ -878,8 +878,8 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa } // calculate bounds - bounds.expandToCoverVertex(tempVerticesData[0].x, tempVerticesData[0].y); - bounds.expandToCoverVertex(tempVerticesData[1].x, tempVerticesData[1].y); + bounds.expandToCover(tempVerticesData[0].x, tempVerticesData[0].y); + bounds.expandToCover(tempVerticesData[1].x, tempVerticesData[1].y); } // since multiple objects tessellated into buffer, separate them with degen tris diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index b4a201ed2374..b96640144657 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -37,21 +37,34 @@ class RenderNode; struct Vertex; /** - * The provided macro is executed for each op type in order, with the results separated by commas. + * On of the provided macros is executed for each op type in order. The first will be used for ops + * that cannot merge, and the second for those that can. * * This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs. */ +#define MAP_OPS_BASED_ON_MERGEABILITY(U_OP_FN, M_OP_FN) \ + M_OP_FN(BitmapOp) \ + U_OP_FN(LinesOp) \ + U_OP_FN(RectOp) \ + U_OP_FN(RenderNodeOp) \ + U_OP_FN(ShadowOp) \ + U_OP_FN(SimpleRectsOp) \ + M_OP_FN(TextOp) \ + U_OP_FN(BeginLayerOp) \ + U_OP_FN(EndLayerOp) \ + U_OP_FN(LayerOp) + +/** + * The provided macro is executed for each op type in order. This is used in cases where + * merge-ability of ops doesn't matter. + */ #define MAP_OPS(OP_FN) \ - OP_FN(BitmapOp) \ - OP_FN(LinesOp) \ - OP_FN(RectOp) \ - OP_FN(RenderNodeOp) \ - OP_FN(ShadowOp) \ - OP_FN(SimpleRectsOp) \ - OP_FN(TextOp) \ - OP_FN(BeginLayerOp) \ - OP_FN(EndLayerOp) \ - OP_FN(LayerOp) + MAP_OPS_BASED_ON_MERGEABILITY(OP_FN, OP_FN) + +#define NULL_OP_FN(Type) + +#define MAP_MERGED_OPS(OP_FN) \ + MAP_OPS_BASED_ON_MERGEABILITY(NULL_OP_FN, OP_FN) // Generate OpId enum #define IDENTITY_FN(Type) Type, diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 69c686ec3ba2..e6020cdba266 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -248,10 +248,7 @@ void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPai Rect unmappedBounds(points[0], points[1], points[0], points[1]); for (int i = 2; i < floatCount; i += 2) { - unmappedBounds.left = std::min(unmappedBounds.left, points[i]); - unmappedBounds.right = std::max(unmappedBounds.right, points[i]); - unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]); - unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]); + unmappedBounds.expandToCover(points[i], points[i + 1]); } // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced @@ -413,6 +410,7 @@ void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, i glyphs = refBuffer<glyph_t>(glyphs, glyphCount); positions = refBuffer<float>(positions, glyphCount * 2); + // TODO: either must account for text shadow in bounds, or record separate ops for text shadows addOp(new (alloc()) TextOp( Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), *(mState.currentSnapshot()->transform), diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 472aad711432..30c925c8775b 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -253,7 +253,18 @@ public: bottom = ceilf(bottom); } - void expandToCoverVertex(float x, float y) { + /* + * Similar to unionWith, except this makes the assumption that both rects are non-empty + * to avoid both emptiness checks. + */ + void expandToCover(const Rect& other) { + left = std::min(left, other.left); + top = std::min(top, other.top); + right = std::max(right, other.right); + bottom = std::max(bottom, other.bottom); + } + + void expandToCover(float x, float y) { left = std::min(left, x); top = std::min(top, y); right = std::max(right, x); diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index a6c72a380805..21901cf4414b 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -138,7 +138,7 @@ bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) { // in the cache (and is thus added to the cache) Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) { - AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap); + AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef()); if (CC_UNLIKELY(entry)) { return entry->texture; } diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h index c0373aceebba..bdb5b7b381bf 100644 --- a/libs/hwui/VertexBuffer.h +++ b/libs/hwui/VertexBuffer.h @@ -118,7 +118,7 @@ public: TYPE* end = current + vertexCount; mBounds.set(current->x, current->y, current->x, current->y); for (; current < end; current++) { - mBounds.expandToCoverVertex(current->x, current->y); + mBounds.expandToCover(current->x, current->y); } } diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 9c1c0b9d2c08..0af99398b5f5 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -103,10 +103,11 @@ public: return snapshot; } - static SkBitmap createSkBitmap(int width, int height) { + static SkBitmap createSkBitmap(int width, int height, + SkColorType colorType = kN32_SkColorType) { SkBitmap bitmap; SkImageInfo info = SkImageInfo::Make(width, height, - kN32_SkColorType, kPremul_SkAlphaType); + colorType, kPremul_SkAlphaType); bitmap.setInfo(info); bitmap.allocPixels(info); return bitmap; diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index 27adb125fcf9..6c64a327013e 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -62,7 +62,9 @@ public: int cardIndexOffset = scrollPx / (cardSpacing + cardHeight); int pxOffset = -(scrollPx % (cardSpacing + cardHeight)); - TestCanvas canvas(cardWidth, cardHeight); + TestCanvas canvas( + listView->stagingProperties().getWidth(), + listView->stagingProperties().getHeight()); for (size_t ci = 0; ci < cards.size(); ci++) { // update card position auto card = cards[(ci + cardIndexOffset) % cards.size()]; @@ -121,9 +123,11 @@ private: static SkBitmap filledBox = createBoxBitmap(true); static SkBitmap strokedBox = createBoxBitmap(false); - props.mutableOutline().setRoundRect(0, 0, cardWidth, cardHeight, dp(6), 1); - props.mutableOutline().setShouldClip(true); - canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); + // TODO: switch to using round rect clipping, once merging correctly handles that + SkPaint roundRectPaint; + roundRectPaint.setAntiAlias(true); + roundRectPaint.setColor(Color::White); + canvas.drawRoundRect(0, 0, cardWidth, cardHeight, dp(6), dp(6), roundRectPaint); SkPaint textPaint; textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); diff --git a/libs/hwui/tests/microbench/OpReordererBench.cpp b/libs/hwui/tests/microbench/OpReordererBench.cpp index 406bfcc684b1..ac2b15cc620a 100644 --- a/libs/hwui/tests/microbench/OpReordererBench.cpp +++ b/libs/hwui/tests/microbench/OpReordererBench.cpp @@ -25,7 +25,7 @@ #include "RecordingCanvas.h" #include "tests/common/TestUtils.h" #include "Vector.h" -#include "microbench/MicroBench.h" +#include "tests/microbench/MicroBench.h" #include <vector> diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp index 98a430a0ef10..068e832bac5c 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -65,12 +65,22 @@ public: virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {} virtual void endFrame() {} - // define virtual defaults for direct -#define BASE_OP_METHOD(Type) \ + // define virtual defaults for single draw methods +#define X(Type) \ virtual void on##Type(const Type&, const BakedOpState&) { \ ADD_FAILURE() << #Type " not expected in this test"; \ } - MAP_OPS(BASE_OP_METHOD) + MAP_OPS(X) +#undef X + + // define virtual defaults for merged draw methods +#define X(Type) \ + virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \ + ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \ + } + MAP_MERGED_OPS(X) +#undef X + int getIndex() { return mIndex; } protected: @@ -83,11 +93,21 @@ protected: */ class TestDispatcher { public: -#define DISPATCHER_METHOD(Type) \ + // define single op methods, which redirect to TestRendererBase +#define X(Type) \ static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \ renderer.on##Type(op, state); \ } - MAP_OPS(DISPATCHER_METHOD); + MAP_OPS(X); +#undef X + + // define merged op methods, which redirect to TestRendererBase +#define X(Type) \ + static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \ + renderer.onMerged##Type##s(opList); \ + } + MAP_MERGED_OPS(X); +#undef X }; class FailRenderer : public TestRendererBase {}; @@ -153,7 +173,8 @@ TEST(OpReorderer, simpleBatching) { auto node = TestUtils::createNode(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - SkBitmap bitmap = TestUtils::createSkBitmap(10, 10); + SkBitmap bitmap = TestUtils::createSkBitmap(10, 10, + kAlpha_8_SkColorType); // Disable merging by using alpha 8 bitmap // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. @@ -171,7 +192,7 @@ TEST(OpReorderer, simpleBatching) { SimpleBatchingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(2 * LOOPS, renderer.getIndex()) - << "Expect number of ops = 2 * loop count"; // TODO: force no merging + << "Expect number of ops = 2 * loop count"; } TEST(OpReorderer, textStrikethroughBatching) { @@ -181,8 +202,10 @@ TEST(OpReorderer, textStrikethroughBatching) { void onRectOp(const RectOp& op, const BakedOpState& state) override { EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text"; } - void onTextOp(const TextOp& op, const BakedOpState& state) override { - EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects"; + void onMergedTextOps(const MergedBakedOpList& opList) override { + EXPECT_EQ(0, mIndex); + mIndex += opList.count; + EXPECT_EQ(5u, opList.count); } }; auto node = TestUtils::createNode(0, 0, 200, 2000, |