diff options
Diffstat (limited to 'libs')
41 files changed, 872 insertions, 688 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index cc0943f52fdb..eaade34a1db6 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -209,6 +209,7 @@ endif LOCAL_SRC_FILES += \ tests/TestContext.cpp \ + tests/TreeContentAnimation.cpp \ tests/main.cpp include $(BUILD_EXECUTABLE) diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index c128ca775155..eca71c6e0e8d 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -28,10 +28,21 @@ CanvasState::CanvasState(CanvasStateClient& renderer) , mWidth(-1) , mHeight(-1) , mSaveCount(1) - , mFirstSnapshot(new Snapshot) , mCanvas(renderer) - , mSnapshot(mFirstSnapshot) { + , mSnapshot(&mFirstSnapshot) { +} + +CanvasState::~CanvasState() { + // First call freeSnapshot on all but mFirstSnapshot + // to invoke all the dtors + freeAllSnapshots(); + // Now actually release the memory + while (mSnapshotPool) { + void* temp = mSnapshotPool; + mSnapshotPool = mSnapshotPool->previous; + free(temp); + } } void CanvasState::initializeSaveStack( @@ -41,11 +52,12 @@ void CanvasState::initializeSaveStack( if (mWidth != viewportWidth || mHeight != viewportHeight) { mWidth = viewportWidth; mHeight = viewportHeight; - mFirstSnapshot->initializeViewport(viewportWidth, viewportHeight); + mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); mCanvas.onViewportInitialized(); } - mSnapshot = new Snapshot(mFirstSnapshot, + freeAllSnapshots(); + mSnapshot = allocSnapshot(&mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); mSnapshot->fbo = mCanvas.getTargetFbo(); @@ -53,6 +65,38 @@ void CanvasState::initializeSaveStack( mSaveCount = 1; } +Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) { + void* memory; + if (mSnapshotPool) { + memory = mSnapshotPool; + mSnapshotPool = mSnapshotPool->previous; + mSnapshotPoolCount--; + } else { + memory = malloc(sizeof(Snapshot)); + } + return new (memory) Snapshot(previous, savecount); +} + +void CanvasState::freeSnapshot(Snapshot* snapshot) { + snapshot->~Snapshot(); + // Arbitrary number, just don't let this grown unbounded + if (mSnapshotPoolCount > 10) { + free((void*) snapshot); + } else { + snapshot->previous = mSnapshotPool; + mSnapshotPool = snapshot; + mSnapshotPoolCount++; + } +} + +void CanvasState::freeAllSnapshots() { + while (mSnapshot != &mFirstSnapshot) { + Snapshot* temp = mSnapshot; + mSnapshot = mSnapshot->previous; + freeSnapshot(temp); + } +} + /////////////////////////////////////////////////////////////////////////////// // Save (layer) /////////////////////////////////////////////////////////////////////////////// @@ -64,7 +108,7 @@ void CanvasState::initializeSaveStack( * stack, and ensures restoreToCount() doesn't call back into subclass overrides. */ int CanvasState::saveSnapshot(int flags) { - mSnapshot = new Snapshot(mSnapshot, flags); + mSnapshot = allocSnapshot(mSnapshot, flags); return mSaveCount++; } @@ -76,14 +120,16 @@ int CanvasState::save(int flags) { * Guaranteed to restore without side-effects. */ void CanvasState::restoreSnapshot() { - sp<Snapshot> toRemove = mSnapshot; - sp<Snapshot> toRestore = mSnapshot->previous; + Snapshot* toRemove = mSnapshot; + Snapshot* toRestore = mSnapshot->previous; mSaveCount--; mSnapshot = toRestore; // subclass handles restore implementation mCanvas.onSnapshotRestored(*toRemove, *toRestore); + + freeSnapshot(toRemove); } void CanvasState::restore() { diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h index f0fb9ba8b324..be57f44210ef 100644 --- a/libs/hwui/CanvasState.h +++ b/libs/hwui/CanvasState.h @@ -17,12 +17,12 @@ #ifndef ANDROID_HWUI_CANVAS_STATE_H #define ANDROID_HWUI_CANVAS_STATE_H +#include "Snapshot.h" + #include <SkMatrix.h> #include <SkPath.h> #include <SkRegion.h> -#include "Snapshot.h" - namespace android { namespace uirenderer { @@ -74,6 +74,7 @@ public: class CanvasState { public: CanvasState(CanvasStateClient& renderer); + ~CanvasState(); /** * Initializes the first snapshot, computing the projection matrix, @@ -157,11 +158,15 @@ public: int getHeight() const { return mHeight; } bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); } - inline const Snapshot* currentSnapshot() const { return mSnapshot.get(); } - inline Snapshot* writableSnapshot() { return mSnapshot.get(); } - inline const Snapshot* firstSnapshot() const { return mFirstSnapshot.get(); } + inline const Snapshot* currentSnapshot() const { return mSnapshot; } + inline Snapshot* writableSnapshot() { return mSnapshot; } + inline const Snapshot* firstSnapshot() const { return &mFirstSnapshot; } private: + Snapshot* allocSnapshot(Snapshot* previous, int savecount); + void freeSnapshot(Snapshot* snapshot); + void freeAllSnapshots(); + /// indicates that the clip has been changed since the last time it was consumed bool mDirtyClip; @@ -172,13 +177,18 @@ private: int mSaveCount; /// Base state - sp<Snapshot> mFirstSnapshot; + Snapshot mFirstSnapshot; /// Host providing callbacks CanvasStateClient& mCanvas; /// Current state - sp<Snapshot> mSnapshot; + Snapshot* mSnapshot; + + // Pool of allocated snapshots to re-use + // NOTE: The dtors have already been invoked! + Snapshot* mSnapshotPool = nullptr; + int mSnapshotPoolCount = 0; }; // class CanvasState diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index 8e7efb4e35d6..a9d1e4284d2e 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -23,14 +23,6 @@ namespace android { namespace uirenderer { -static bool intersect(Rect& r, const Rect& r2) { - bool hasIntersection = r.intersect(r2); - if (!hasIntersection) { - r.setEmpty(); - } - return hasIntersection; -} - static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { Vertex v = {x, y}; transform.mapPoint(v.x, v.y); @@ -67,9 +59,8 @@ bool TransformedRectangle::canSimplyIntersectWith( return mTransform == other.mTransform; } -bool TransformedRectangle::intersectWith(const TransformedRectangle& other) { - Rect translatedBounds(other.mBounds); - return intersect(mBounds, translatedBounds); +void TransformedRectangle::intersectWith(const TransformedRectangle& other) { + mBounds.doIntersect(other.mBounds); } bool TransformedRectangle::isEmpty() const { @@ -146,7 +137,7 @@ Rect RectangleList::calculateBounds() const { if (index == 0) { bounds = tr.transformedBounds(); } else { - bounds.intersect(tr.transformedBounds()); + bounds.doIntersect(tr.transformedBounds()); } } return bounds; @@ -275,10 +266,7 @@ void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, if (transform->rectToRect()) { Rect transformed(r); transform->mapRect(transformed); - bool hasIntersection = mClipRect.intersect(transformed); - if (!hasIntersection) { - mClipRect.setEmpty(); - } + mClipRect.doIntersect(transformed); return; } diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h index 38fefe5ab097..f88fd92e234d 100644 --- a/libs/hwui/ClipArea.h +++ b/libs/hwui/ClipArea.h @@ -33,7 +33,7 @@ public: TransformedRectangle(const Rect& bounds, const Matrix4& transform); bool canSimplyIntersectWith(const TransformedRectangle& other) const; - bool intersectWith(const TransformedRectangle& other); + void intersectWith(const TransformedRectangle& other); bool isEmpty() const; diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index a81ffb9f59fa..0c29a9e928a2 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -44,6 +44,12 @@ namespace uirenderer { #define DEBUG_COLOR_MERGEDBATCH 0x5f7f7fff #define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f +static bool avoidOverdraw() { + // Don't avoid overdraw when visualizing it, since that makes it harder to + // debug where it's coming from, and when the problem occurs. + return !Properties::debugOverdraw; +}; + ///////////////////////////////////////////////////////////////////////////////// // Operation Batches ///////////////////////////////////////////////////////////////////////////////// @@ -218,7 +224,10 @@ public: // if paints are equal, then modifiers + paint attribs don't need to be compared if (op->mPaint == mOps[0].op->mPaint) return true; - if (op->getPaintAlpha() != mOps[0].op->getPaintAlpha()) return false; + if (PaintUtils::getAlphaDirect(op->mPaint) + != PaintUtils::getAlphaDirect(mOps[0].op->mPaint)) { + return false; + } if (op->mPaint && mOps[0].op->mPaint && op->mPaint->getColorFilter() != mOps[0].op->mPaint->getColorFilter()) { @@ -495,7 +504,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { && mSaveStack.empty() && !state->mRoundRectClipState; - if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() && + if (CC_LIKELY(avoidOverdraw()) && mBatches.size() && state->mClipSideFlags != kClipSide_ConservativeFull && deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) { // avoid overdraw by resetting drawing state + discarding drawing ops @@ -533,7 +542,11 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { if (deferInfo.mergeable) { // Try to merge with any existing batch with same mergeId. - if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) { + std::unordered_map<mergeid_t, DrawBatch*>& mergingBatch + = mMergingBatches[deferInfo.batchId]; + auto getResult = mergingBatch.find(deferInfo.mergeId); + if (getResult != mergingBatch.end()) { + targetBatch = getResult->second; if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) { targetBatch = nullptr; } @@ -577,7 +590,8 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { if (deferInfo.mergeable) { targetBatch = new MergingDrawBatch(deferInfo, renderer.getViewportWidth(), renderer.getViewportHeight()); - mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch); + mMergingBatches[deferInfo.batchId].insert( + std::make_pair(deferInfo.mergeId, targetBatch)); } else { targetBatch = new DrawBatch(deferInfo); mBatchLookup[deferInfo.batchId] = targetBatch; @@ -642,7 +656,7 @@ void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { // save and restore so that reordering doesn't affect final state renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); - if (CC_LIKELY(mAvoidOverdraw)) { + if (CC_LIKELY(avoidOverdraw())) { for (unsigned int i = 1; i < mBatches.size(); i++) { if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) { discardDrawingBatches(i - 1); diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 4f2dca5f3ee1..7873fbdd342a 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -17,9 +17,10 @@ #ifndef ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H #define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H +#include <unordered_map> + #include <utils/Errors.h> #include <utils/LinearAllocator.h> -#include <utils/TinyHashMap.h> #include "Matrix.h" #include "OpenGLRenderer.h" @@ -82,8 +83,8 @@ public: class DeferredDisplayList { friend struct DeferStateStruct; // used to give access to allocator public: - DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) : - mBounds(bounds), mAvoidOverdraw(avoidOverdraw) { + DeferredDisplayList(const Rect& bounds) + : mBounds(bounds) { clear(); } ~DeferredDisplayList() { clear(); } @@ -151,7 +152,6 @@ private: // layer space bounds of rendering Rect mBounds; - const bool mAvoidOverdraw; /** * At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so @@ -177,7 +177,7 @@ private: * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not * collide, which avoids the need to resolve mergeid collisions. */ - TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]; + std::unordered_map<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]; LinearAllocator mAllocator; }; diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index 38f2363f3532..70383340fc8d 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -47,7 +47,8 @@ DeferredLayerUpdater::~DeferredLayerUpdater() { } void DeferredLayerUpdater::setPaint(const SkPaint* paint) { - OpenGLRenderer::getAlphaAndModeDirect(paint, &mAlpha, &mMode); + mAlpha = PaintUtils::getAlphaDirect(paint); + mMode = PaintUtils::getXfermodeDirect(paint); SkColorFilter* colorFilter = (paint) ? paint->getColorFilter() : nullptr; SkRefCnt_SafeAssign(mColorFilter, colorFilter); } diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index dc5cb8b349f1..ddfc533f9d77 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -172,10 +172,6 @@ public: void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; } bool getQuickRejected() { return mQuickRejected; } - inline int getPaintAlpha() const { - return OpenGLRenderer::getAlphaDirect(mPaint); - } - virtual bool hasTextShadow() const { return false; } @@ -213,7 +209,7 @@ protected: if (state.mAlpha != 1.0f) return false; - SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(mPaint); return (mode == SkXfermode::kSrcOver_Mode || mode == SkXfermode::kSrc_Mode); @@ -249,8 +245,8 @@ public: virtual bool getLocalBounds(Rect& localBounds) override { localBounds.set(mLocalBounds); - OpenGLRenderer::TextShadow textShadow; - if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) { + PaintUtils::TextShadow textShadow; + if (PaintUtils::getTextShadow(mPaint, &textShadow)) { Rect shadow(mLocalBounds); shadow.translate(textShadow.dx, textShadow.dx); shadow.outset(textShadow.radius); @@ -372,8 +368,8 @@ public: private: bool isSaveLayerAlpha() const { - SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); - int alpha = OpenGLRenderer::getAlphaDirect(mPaint); + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(mPaint); + int alpha = PaintUtils::getAlphaDirect(mPaint); return alpha < 255 && mode == SkXfermode::kSrcOver_Mode; } @@ -691,7 +687,7 @@ public: // TODO: support clipped bitmaps by handling them in SET_TEXTURE deferInfo.mergeable = state.mMatrix.isSimple() && state.mMatrix.positiveScale() && !state.mClipSideFlags && - OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode && + PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode && (mBitmap->colorType() != kAlpha_8_SkColorType); } @@ -895,7 +891,7 @@ public: deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch; deferInfo.mergeId = getAtlasEntry(renderer) ? (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap; deferInfo.mergeable = state.mMatrix.isPureTranslate() && - OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && mBitmap->isOpaque(); } @@ -1241,7 +1237,7 @@ public: } virtual bool hasTextShadow() const override { - return OpenGLRenderer::hasTextShadow(mPaint); + return PaintUtils::hasTextShadow(mPaint); } virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, @@ -1330,7 +1326,7 @@ public: deferInfo.mergeable = state.mMatrix.isPureTranslate() && !hasDecorations - && OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + && PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; } virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override { diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 4b9d4f90675c..ccf0b48cd4be 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -667,14 +667,6 @@ bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, cons return mDrawn; } -void FontRenderer::removeFont(const Font* font) { - mActiveFonts.remove(font->getDescription()); - - if (mCurrentFont == font) { - mCurrentFont = nullptr; - } -} - void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) { uint32_t intRadius = Blur::convertRadiusToInt(radius); #ifdef ANDROID_ENABLE_RENDERSCRIPT diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 936c838bd6e4..8172312e9a43 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -147,8 +147,6 @@ private: float x3, float y3, float u3, float v3, float x4, float y4, float u4, float v4, CacheTexture* texture); - void removeFont(const Font* font); - void checkTextureUpdate(); void setTextureDirty() { diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h index fa20b0807a88..4785ea48cddc 100644 --- a/libs/hwui/Glop.h +++ b/libs/hwui/Glop.h @@ -135,10 +135,6 @@ struct Glop { } fill; struct Transform { - // Orthographic projection matrix for current FBO - // TODO: move out of Glop, since this is static per FBO - Matrix4 ortho; - // modelView transform, accounting for delta between mesh transform and content of the mesh // often represents x/y offsets within command, or scaling for mesh unit size Matrix4 modelView; diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 69559a77c3a0..fa166ae5ca5a 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -461,11 +461,10 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) { // Transform //////////////////////////////////////////////////////////////////////////////// -void GlopBuilder::setTransform(const Matrix4& ortho, const Matrix4& canvas, +void GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) { TRIGGER_STAGE(kTransformStage); - mOutGlop->transform.ortho = ortho; mOutGlop->transform.canvas = canvas; mOutGlop->transform.transformFlags = transformFlags; } diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 549bb21e5f8d..8d05570dd206 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -71,7 +71,7 @@ public: GlopBuilder& setFillTextureLayer(Layer& layer, float alpha); GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) { - setTransform(snapshot.getOrthoMatrix(), *snapshot.transform, transformFlags); + setTransform(*snapshot.transform, transformFlags); return *this; } @@ -102,8 +102,7 @@ private: void setFill(int color, float alphaScale, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage, const SkShader* shader, const SkColorFilter* colorFilter); - void setTransform(const Matrix4& ortho, const Matrix4& canvas, - const int transformFlags); + void setTransform(const Matrix4& canvas, const int transformFlags); enum StageFlags { kInitialStage = 0, diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 8d8528961794..f99d92b89420 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -170,7 +170,8 @@ void Layer::updateDeferred(RenderNode* renderNode, int left, int top, int right, } void Layer::setPaint(const SkPaint* paint) { - OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); + alpha = PaintUtils::getAlphaDirect(paint); + mode = PaintUtils::getXfermodeDirect(paint); setColorFilter((paint) ? paint->getColorFilter() : nullptr); } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index c63b5597f284..227271d83cf8 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -58,7 +58,7 @@ void LayerRenderer::prepareDirty(int viewportWidth, int viewportHeight, mLayer->region.clear(); dirty.set(0.0f, 0.0f, width, height); } else { - dirty.intersect(0.0f, 0.0f, width, height); + dirty.doIntersect(0.0f, 0.0f, width, height); android::Rect r(dirty.left, dirty.top, dirty.right, dirty.bottom); mLayer->region.subtractSelf(r); } diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index a401ce119021..cd03ac407d81 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -488,7 +488,8 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool currentTransform()->mapRect(bounds); // Layers only make sense if they are in the framebuffer's bounds - if (bounds.intersect(mState.currentClipRect())) { + bounds.doIntersect(mState.currentClipRect()); + if (!bounds.isEmpty()) { // We cannot work with sub-pixels in this case bounds.snapToPixelBoundaries(); @@ -497,23 +498,20 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool // of the framebuffer const Snapshot& previous = *(currentSnapshot()->previous); Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight()); - if (!bounds.intersect(previousViewport)) { - bounds.setEmpty(); - } else if (fboLayer) { + + bounds.doIntersect(previousViewport); + if (!bounds.isEmpty() && fboLayer) { clip.set(bounds); mat4 inverse; inverse.loadInverse(*currentTransform()); inverse.mapRect(clip); clip.snapToPixelBoundaries(); - if (clip.intersect(untransformedBounds)) { + clip.doIntersect(untransformedBounds); + if (!clip.isEmpty()) { clip.translate(-untransformedBounds.left, -untransformedBounds.top); bounds.set(untransformedBounds); - } else { - clip.setEmpty(); } } - } else { - bounds.setEmpty(); } } @@ -540,7 +538,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float Rect bounds(left, top, right, bottom); Rect clip; calculateLayerBoundsAndClip(bounds, clip, true); - updateSnapshotIgnoreForLayer(bounds, clip, true, getAlphaDirect(paint)); + updateSnapshotIgnoreForLayer(bounds, clip, true, PaintUtils::getAlphaDirect(paint)); if (!mState.currentlyIgnored()) { writableSnapshot()->resetTransform(-bounds.left, -bounds.top, 0.0f); @@ -615,7 +613,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto Rect clip; Rect bounds(left, top, right, bottom); calculateLayerBoundsAndClip(bounds, clip, fboLayer); - updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, getAlphaDirect(paint)); + updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, PaintUtils::getAlphaDirect(paint)); // Bail out if we won't draw in this snapshot if (mState.currentlyIgnored()) { @@ -1038,7 +1036,8 @@ void OpenGLRenderer::dirtyLayer(const float left, const float top, } void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { - if (CC_LIKELY(!bounds.isEmpty() && bounds.intersect(mState.currentClipRect()))) { + bounds.doIntersect(mState.currentClipRect()); + if (!bounds.isEmpty()) { bounds.snapToPixelBoundaries(); android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom); if (!dirty.isEmpty()) { @@ -1112,7 +1111,8 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // is used, it should more closely duplicate the quickReject logic (in how it uses // snapToPixelBoundaries) - if (!clippedBounds.intersect(currentClip)) { + clippedBounds.doIntersect(currentClip); + if (clippedBounds.isEmpty()) { // quick rejected return true; } @@ -1242,9 +1242,8 @@ void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) { Rect bounds = tr.getBounds(); if (transform.rectToRect()) { transform.mapRect(bounds); - if (!bounds.intersect(scissorBox)) { - bounds.setEmpty(); - } else { + bounds.doIntersect(scissorBox); + if (!bounds.isEmpty()) { handlePointNoTransform(rectangleVertices, bounds.left, bounds.top); handlePointNoTransform(rectangleVertices, bounds.right, bounds.top); handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom); @@ -1405,7 +1404,7 @@ void OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type) { setStencilFromClip(); } - mRenderState.render(glop); + mRenderState.render(glop, currentSnapshot()->getOrthoMatrix()); if (type == GlopRenderType::Standard && !mRenderState.stencil().isWriteEnabled()) { // TODO: specify more clearly when a draw should dirty the layer. // is writing to the stencil the only time we should ignore this? @@ -1431,10 +1430,7 @@ void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t return; } - // Don't avoid overdraw when visualizing, since that makes it harder to - // debug where it's coming from, and when the problem occurs. - bool avoidOverdraw = !Properties::debugOverdraw; - DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw); + DeferredDisplayList deferredList(mState.currentClipRect()); DeferStateStruct deferStruct(deferredList, *this, replayFlags); renderNode->defer(deferStruct, 0); @@ -1958,8 +1954,8 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, FontRenderer& fontRenderer, int alpha, float x, float y) { mCaches.textureState().activateTexture(0); - TextShadow textShadow; - if (!getTextShadow(paint, &textShadow)) { + PaintUtils::TextShadow textShadow; + if (!PaintUtils::getTextShadow(paint, &textShadow)) { LOG_ALWAYS_FATAL("failed to query shadow attributes"); } @@ -1987,8 +1983,10 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, renderGlop(glop); } +// TODO: remove this, once mState.currentlyIgnored captures snapshot alpha bool OpenGLRenderer::canSkipText(const SkPaint* paint) const { - float alpha = (hasTextShadow(paint) ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha; + float alpha = (PaintUtils::hasTextShadow(paint) + ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha; return MathUtils::isZero(alpha) && PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode; } @@ -2017,11 +2015,10 @@ void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); fontRenderer.setFont(paint, SkMatrix::I()); - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); + int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); - if (CC_UNLIKELY(hasTextShadow(paint))) { + if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) { drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, alpha, 0.0f, 0.0f); } @@ -2162,13 +2159,12 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float y = floorf(y + transform.getTranslateY() + 0.5f); } - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); + int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); - if (CC_UNLIKELY(hasTextShadow(paint))) { + if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) { fontRenderer.setFont(paint, SkMatrix::I()); drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, alpha, oldX, oldY); @@ -2238,9 +2234,8 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, fontRenderer.setFont(paint, SkMatrix::I()); fontRenderer.setTextureFiltering(true); - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); + int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); const Rect* clip = &writableSnapshot()->getLocalClip(); @@ -2530,12 +2525,6 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot renderGlop(glop); } -void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, - SkXfermode::Mode* mode) const { - getAlphaAndModeDirect(paint, alpha, mode); - *alpha *= currentSnapshot()->alpha; -} - float OpenGLRenderer::getLayerAlpha(const Layer* layer) const { return (layer->getAlpha() / 255.0f) * currentSnapshot()->alpha; } diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 910af5705705..400c225b53a0 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -260,57 +260,6 @@ public: void endMark() const; /** - * Gets the alpha and xfermode out of a paint object. If the paint is null - * alpha will be 255 and the xfermode will be SRC_OVER. This method does - * not multiply the paint's alpha by the current snapshot's alpha, and does - * not replace the alpha with the overrideLayerAlpha - * - * @param paint The paint to extract values from - * @param alpha Where to store the resulting alpha - * @param mode Where to store the resulting xfermode - */ - static inline void getAlphaAndModeDirect(const SkPaint* paint, int* alpha, - SkXfermode::Mode* mode) { - *mode = getXfermodeDirect(paint); - *alpha = getAlphaDirect(paint); - } - - static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) { - if (!paint) return SkXfermode::kSrcOver_Mode; - return PaintUtils::getXfermode(paint->getXfermode()); - } - - static inline int getAlphaDirect(const SkPaint* paint) { - if (!paint) return 255; - return paint->getAlpha(); - } - - struct TextShadow { - SkScalar radius; - float dx; - float dy; - SkColor color; - }; - - static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) { - SkDrawLooper::BlurShadowRec blur; - if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) { - if (textShadow) { - textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma); - textShadow->dx = blur.fOffset.fX; - textShadow->dy = blur.fOffset.fY; - textShadow->color = blur.fColor; - } - return true; - } - return false; - } - - static inline bool hasTextShadow(const SkPaint* paint) { - return getTextShadow(paint, nullptr); - } - - /** * Build the best transform to use to rasterize text given a full * transform matrix, and whether filteration is needed. * @@ -493,16 +442,6 @@ protected: void drawTextureLayer(Layer* layer, const Rect& rect); /** - * Gets the alpha and xfermode out of a paint object. If the paint is null - * alpha will be 255 and the xfermode will be SRC_OVER. Accounts for snapshot alpha. - * - * @param paint The paint to extract values from - * @param alpha Where to store the resulting alpha - * @param mode Where to store the resulting xfermode - */ - inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const; - - /** * Gets the alpha from a layer, accounting for snapshot alpha * * @param layer The layer from which the alpha is extracted diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 4c4cd3da3be4..50199db75640 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -125,25 +125,32 @@ public: } bool intersects(float l, float t, float r, float b) const { - return !intersectWith(l, t, r, b).isEmpty(); + float tempLeft = std::max(left, l); + float tempTop = std::max(top, t); + float tempRight = std::min(right, r); + float tempBottom = std::min(bottom, b); + + return ((tempLeft < tempRight) && (tempTop < tempBottom)); // !isEmpty } bool intersects(const Rect& r) const { return intersects(r.left, r.top, r.right, r.bottom); } - bool intersect(float l, float t, float r, float b) { - Rect tmp(l, t, r, b); - intersectWith(tmp); - if (!tmp.isEmpty()) { - set(tmp); - return true; - } - return false; + /** + * This method is named 'doIntersect' instead of 'intersect' so as not to be confused with + * SkRect::intersect / android.graphics.Rect#intersect behavior, which do not modify the object + * if the intersection of the rects would be empty. + */ + void doIntersect(float l, float t, float r, float b) { + left = std::max(left, l); + top = std::max(top, t); + right = std::min(right, r); + bottom = std::min(bottom, b); } - bool intersect(const Rect& r) { - return intersect(r.left, r.top, r.right, r.bottom); + void doIntersect(const Rect& r) { + doIntersect(r.left, r.top, r.right, r.bottom); } inline bool contains(float l, float t, float r, float b) const { @@ -271,24 +278,6 @@ public: void dump(const char* label = nullptr) const { ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom); } - -private: - void intersectWith(Rect& tmp) const { - tmp.left = std::max(left, tmp.left); - tmp.top = std::max(top, tmp.top); - tmp.right = std::min(right, tmp.right); - tmp.bottom = std::min(bottom, tmp.bottom); - } - - Rect intersectWith(float l, float t, float r, float b) const { - Rect tmp; - tmp.left = std::max(left, l); - tmp.top = std::max(top, t); - tmp.right = std::min(right, r); - tmp.bottom = std::min(bottom, b); - return tmp; - } - }; // class Rect }; // namespace uirenderer diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index ddc7ecd329b6..bf1b4d0b0d0e 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -725,7 +725,9 @@ template <class T> void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler) { if (properties().getAlpha() <= 0.0f || properties().getOutline().getAlpha() <= 0.0f - || !properties().getOutline().getPath()) { + || !properties().getOutline().getPath() + || properties().getScaleX() == 0 + || properties().getScaleY() == 0) { // no shadow to draw return; } @@ -915,7 +917,10 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { const bool useViewProperties = (!mLayer || drawLayer); if (useViewProperties) { const Outline& outline = properties().getOutline(); - if (properties().getAlpha() <= 0 || (outline.getShouldClip() && outline.isEmpty())) { + if (properties().getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties().getScaleX() == 0 + || properties().getScaleY() == 0) { DISPLAY_LIST_LOGD("%*sRejected display list (%p, %s)", handler.level() * 2, "", this, getName()); return; diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index ad74bff8dc25..ce1bd6ab8b03 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -52,11 +52,8 @@ bool LayerProperties::setColorFilter(SkColorFilter* filter) { bool LayerProperties::setFromPaint(const SkPaint* paint) { bool changed = false; - SkXfermode::Mode mode; - int alpha; - OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); - changed |= setAlpha(static_cast<uint8_t>(alpha)); - changed |= setXferMode(mode); + changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint))); + changed |= setXferMode(PaintUtils::getXfermodeDirect(paint)); changed |= setColorFilter(paint ? paint->getColorFilter() : nullptr); return changed; } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 71589c802749..f824cc020196 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -549,7 +549,7 @@ public: if (flags & CLIP_TO_BOUNDS) { outRect->set(0, 0, getWidth(), getHeight()); if (flags & CLIP_TO_CLIP_BOUNDS) { - outRect->intersect(mPrimitiveFields.mClipBounds); + outRect->doIntersect(mPrimitiveFields.mClipBounds); } } else { outRect->set(mPrimitiveFields.mClipBounds); diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp index 220936551a60..eb0fa74f5af0 100644 --- a/libs/hwui/ShadowTessellator.cpp +++ b/libs/hwui/ShadowTessellator.cpp @@ -73,8 +73,8 @@ void ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque, } #if DEBUG_SHADOW - ALOGD("light center %f %f %f", - adjustedLightCenter.x, adjustedLightCenter.y, adjustedLightCenter.z); + ALOGD("light center %f %f %f %d", + adjustedLightCenter.x, adjustedLightCenter.y, adjustedLightCenter.z, lightRadius); #endif // light position (because it's in local space) needs to compensate for receiver transform diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index 4d60b8dd0e7c..0a58f4b42e4c 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -44,7 +44,7 @@ Snapshot::Snapshot() * Copies the specified snapshot/ The specified snapshot is stored as * the previous snapshot. */ -Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) +Snapshot::Snapshot(Snapshot* s, int saveFlags) : flags(0) , previous(s) , layer(s->layer) @@ -148,7 +148,7 @@ void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const { const Snapshot* current = this; do { snapshotList.push(current); - current = current->previous.get(); + current = current->previous; } while (current); // traverse the list, adding in each transform that contributes to the total transform @@ -240,7 +240,7 @@ bool Snapshot::isIgnored() const { void Snapshot::dump() const { ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d", - this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple()); + this, flags, previous, getViewportHeight(), isIgnored(), !mClipArea->isSimple()); const Rect& clipRect(mClipArea->getClipRect()); ALOGD(" ClipRect %.1f %.1f %.1f %.1f, clip simple %d", clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, mClipArea->isSimple()); diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index cf8f11c80058..aeeda965c48f 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -83,11 +83,11 @@ public: * Each snapshot has a link to a previous snapshot, indicating the previous * state of the renderer. */ -class Snapshot: public LightRefBase<Snapshot> { +class Snapshot { public: Snapshot(); - Snapshot(const sp<Snapshot>& s, int saveFlags); + Snapshot(Snapshot* s, int saveFlags); /** * Various flags set on ::flags. @@ -229,7 +229,7 @@ public: /** * Previous snapshot. */ - sp<Snapshot> previous; + Snapshot* previous; /** * A pointer to the currently active layer. diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp index 9b0a1aadf0bf..bdce73c79993 100644 --- a/libs/hwui/SpotShadow.cpp +++ b/libs/hwui/SpotShadow.cpp @@ -1051,7 +1051,7 @@ void SpotShadow::dumpPolygon(const Vector2* poly, int polyLength, const char* po */ void SpotShadow::dumpPolygon(const Vector3* poly, int polyLength, const char* polyName) { for (int i = 0; i < polyLength; i++) { - ALOGD("polygon %s i %d x %f y %f", polyName, i, poly[i].x, poly[i].y); + ALOGD("polygon %s i %d x %f y %f z %f", polyName, i, poly[i].x, poly[i].y, poly[i].z); } } diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index fb0753bbc76c..d680f990a0be 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -61,8 +61,6 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& ras } Font::~Font() { - mState->removeFont(this); - for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { delete mCachedGlyphs.valueAt(i); } diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index c5126def683c..dfa70ace2f44 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -208,7 +208,7 @@ void RenderState::postDecStrong(VirtualLightRefBase* object) { // Render /////////////////////////////////////////////////////////////////////////////// -void RenderState::render(const Glop& glop) { +void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { const Glop::Mesh& mesh = glop.mesh; const Glop::Mesh::Vertices& vertices = mesh.vertices; const Glop::Mesh::Indices& indices = mesh.indices; @@ -223,7 +223,7 @@ void RenderState::render(const Glop& glop) { fill.program->setColor(fill.color); } - fill.program->set(glop.transform.ortho, + fill.program->set(orthoMatrix, glop.transform.modelView, glop.transform.meshTransform(), glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor); diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 4fd792c1b503..9ae084506f1d 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -84,7 +84,7 @@ public: // more thinking... void postDecStrong(VirtualLightRefBase* object); - void render(const Glop& glop); + void render(const Glop& glop, const Matrix4& orthoMatrix); AssetAtlas& assetAtlas() { return mAssetAtlas; } Blend& blend() { return *mBlend; } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 9dc5b45a7738..38f6e539693e 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -62,7 +62,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) , mJankTracker(thread.timeLord().frameIntervalNanos()) , mProfiler(mFrames) - , mContentOverdrawProtectionBounds(0, 0, 0, 0) { + , mContentDrawBounds(0, 0, 0, 0) { mRenderNodes.emplace_back(rootRenderNode); mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); @@ -309,7 +309,7 @@ void CanvasContext::draw() { Rect outBounds; // It there are multiple render nodes, they are as follows: // #0 - backdrop - // #1 - content (with - and clipped to - bounds mContentOverdrawProtectionBounds) + // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) // #2 - frame // Usually the backdrop cannot be seen since it will be entirely covered by the content. While // resizing however it might become partially visible. The following render loop will crop the @@ -317,66 +317,72 @@ void CanvasContext::draw() { // against the backdrop (since that indicates a shrinking of the window) and then the frame // around everything. // The bounds of the backdrop against which the content should be clipped. - Rect backdropBounds = mContentOverdrawProtectionBounds; + Rect backdropBounds = mContentDrawBounds; + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + Rect contentBounds; // If there is no content bounds we ignore the layering as stated above and start with 2. - int layer = mContentOverdrawProtectionBounds.isEmpty() ? 2 : 0; + int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() <= 2) ? 2 : 0; // Draw all render nodes. Note that for (const sp<RenderNode>& node : mRenderNodes) { if (layer == 0) { // Backdrop. - // Draw the backdrop clipped to the inverse content bounds. + // Draw the backdrop clipped to the inverse content bounds, but assume that the content + // was moved to the upper left corner. const RenderProperties& properties = node->properties(); Rect targetBounds(properties.getLeft(), properties.getTop(), properties.getRight(), properties.getBottom()); + // Move the content bounds towards the fixed corner of the backdrop. + const int x = targetBounds.left; + const int y = targetBounds.top; + contentBounds.set(x, y, x + mContentDrawBounds.getWidth(), + y + mContentDrawBounds.getHeight()); // Remember the intersection of the target bounds and the intersection bounds against // which we have to crop the content. - backdropBounds.intersect(targetBounds); + backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); + backdropBounds.doIntersect(targetBounds); // Check if we have to draw something on the left side ... - if (targetBounds.left < mContentOverdrawProtectionBounds.left) { + if (targetBounds.left < contentBounds.left) { mCanvas->save(SkCanvas::kClip_SaveFlag); if (mCanvas->clipRect(targetBounds.left, targetBounds.top, - mContentOverdrawProtectionBounds.left, targetBounds.bottom, + contentBounds.left, targetBounds.bottom, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } // Reduce the target area by the area we have just painted. - targetBounds.left = std::min(mContentOverdrawProtectionBounds.left, - targetBounds.right); + targetBounds.left = std::min(contentBounds.left, targetBounds.right); mCanvas->restore(); } // ... or on the right side ... - if (targetBounds.right > mContentOverdrawProtectionBounds.right && + if (targetBounds.right > contentBounds.right && !targetBounds.isEmpty()) { mCanvas->save(SkCanvas::kClip_SaveFlag); - if (mCanvas->clipRect(mContentOverdrawProtectionBounds.right, targetBounds.top, + if (mCanvas->clipRect(contentBounds.right, targetBounds.top, targetBounds.right, targetBounds.bottom, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } // Reduce the target area by the area we have just painted. - targetBounds.right = std::max(targetBounds.left, - mContentOverdrawProtectionBounds.right); + targetBounds.right = std::max(targetBounds.left, contentBounds.right); mCanvas->restore(); } // ... or at the top ... - if (targetBounds.top < mContentOverdrawProtectionBounds.top && + if (targetBounds.top < contentBounds.top && !targetBounds.isEmpty()) { mCanvas->save(SkCanvas::kClip_SaveFlag); if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right, - mContentOverdrawProtectionBounds.top, + contentBounds.top, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } // Reduce the target area by the area we have just painted. - targetBounds.top = std::min(mContentOverdrawProtectionBounds.top, - targetBounds.bottom); + targetBounds.top = std::min(contentBounds.top, targetBounds.bottom); mCanvas->restore(); } // ... or at the bottom. - if (targetBounds.bottom > mContentOverdrawProtectionBounds.bottom && + if (targetBounds.bottom > contentBounds.bottom && !targetBounds.isEmpty()) { mCanvas->save(SkCanvas::kClip_SaveFlag); - if (mCanvas->clipRect(targetBounds.left, - mContentOverdrawProtectionBounds.bottom, targetBounds.right, + if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right, targetBounds.bottom, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } @@ -384,10 +390,17 @@ void CanvasContext::draw() { } } else if (layer == 1) { // Content // It gets cropped against the bounds of the backdrop to stay inside. - mCanvas->save(SkCanvas::kClip_SaveFlag); - if (mCanvas->clipRect(backdropBounds.left, backdropBounds.top, - backdropBounds.right, backdropBounds.bottom, - SkRegion::kIntersect_Op)) { + mCanvas->save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // We shift and clip the content to match its final location in the window. + const float left = mContentDrawBounds.left; + const float top = mContentDrawBounds.top; + const float dx = backdropBounds.left - left; + const float dy = backdropBounds.top - top; + const float width = backdropBounds.getWidth(); + const float height = backdropBounds.getHeight(); + mCanvas->translate(dx, dy); + if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) { mCanvas->drawRenderNode(node.get(), outBounds); } mCanvas->restore(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 1c3845cac504..e0cbabdc933a 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -126,8 +126,8 @@ public: mRenderNodes.end()); } - void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { - mContentOverdrawProtectionBounds.set(left, top, right, bottom); + void setContentDrawBounds(int left, int top, int right, int bottom) { + mContentDrawBounds.set(left, top, right, bottom); } private: @@ -167,7 +167,7 @@ private: std::set<RenderNode*> mPrefetechedLayers; // Stores the bounds of the main content. - Rect mContentOverdrawProtectionBounds; + Rect mContentDrawBounds; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index f43a769890a4..26aae90d5990 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -529,15 +529,14 @@ void RenderProxy::drawRenderNode(RenderNode* node) { staticPostAndWait(task); } -CREATE_BRIDGE5(setContentOverdrawProtectionBounds, CanvasContext* context, int left, int top, +CREATE_BRIDGE5(setContentDrawBounds, CanvasContext* context, int left, int top, int right, int bottom) { - args->context->setContentOverdrawProtectionBounds(args->left, args->top, args->right, - args->bottom); + args->context->setContentDrawBounds(args->left, args->top, args->right, args->bottom); return nullptr; } -void RenderProxy::setContentOverdrawProtectionBounds(int left, int top, int right, int bottom) { - SETUP_TASK(setContentOverdrawProtectionBounds); +void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) { + SETUP_TASK(setContentDrawBounds); args->context = mContext; args->left = left; args->top = top; diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 046f24ac3f81..d1b62f1f64a6 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -109,7 +109,7 @@ public: ANDROID_API void addRenderNode(RenderNode* node, bool placeFront); ANDROID_API void removeRenderNode(RenderNode* node); ANDROID_API void drawRenderNode(RenderNode* node); - ANDROID_API void setContentOverdrawProtectionBounds(int left, int top, int right, int bottom); + ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom); private: RenderThread& mRenderThread; diff --git a/libs/hwui/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h new file mode 100644 index 000000000000..e16310e034be --- /dev/null +++ b/libs/hwui/tests/Benchmark.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TESTS_BENCHMARK_H +#define TESTS_BENCHMARK_H + +#include <string> +#include <vector> + +namespace android { +namespace uirenderer { + +struct BenchmarkOptions { + int count; +}; + +typedef void (*BenchmarkFunctor)(const BenchmarkOptions&); + +struct BenchmarkInfo { + std::string name; + std::string description; + BenchmarkFunctor functor; +}; + +class Benchmark { +public: + Benchmark(const BenchmarkInfo& info) { + registerBenchmark(info); + } + +private: + Benchmark() = delete; + Benchmark(const Benchmark&) = delete; + Benchmark& operator=(const Benchmark&) = delete; + + static void registerBenchmark(const BenchmarkInfo& info); +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* TESTS_BENCHMARK_H */ diff --git a/libs/hwui/tests/TestContext.cpp b/libs/hwui/tests/TestContext.cpp index cebe7650dea2..ba763a8def62 100644 --- a/libs/hwui/tests/TestContext.cpp +++ b/libs/hwui/tests/TestContext.cpp @@ -22,16 +22,35 @@ namespace test { static const int IDENT_DISPLAYEVENT = 1; -static DisplayInfo getBuiltInDisplay() { +static android::DisplayInfo DUMMY_DISPLAY { + 1080, //w + 1920, //h + 320.0, // xdpi + 320.0, // ydpi + 60.0, // fps + 2.0, // density + 0, // orientation + false, // secure? + 0, // appVsyncOffset + 0, // presentationDeadline + 0, // colorTransform +}; + +DisplayInfo getBuiltInDisplay() { +#if !HWUI_NULL_GPU DisplayInfo display; sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( ISurfaceComposer::eDisplayIdMain)); status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &display); LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n"); return display; +#else + return DUMMY_DISPLAY; +#endif } -android::DisplayInfo gDisplay = getBuiltInDisplay(); +// Initialize to a dummy default +android::DisplayInfo gDisplay = DUMMY_DISPLAY; TestContext::TestContext() { mLooper = new Looper(true); diff --git a/libs/hwui/tests/TestContext.h b/libs/hwui/tests/TestContext.h index 7b30fc1dc7ce..2bbe5dffd9b8 100644 --- a/libs/hwui/tests/TestContext.h +++ b/libs/hwui/tests/TestContext.h @@ -32,6 +32,8 @@ namespace test { extern DisplayInfo gDisplay; #define dp(x) ((x) * android::uirenderer::test::gDisplay.density) +DisplayInfo getBuiltInDisplay(); + class TestContext { public: TestContext(); diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp new file mode 100644 index 000000000000..a59261c14fc5 --- /dev/null +++ b/libs/hwui/tests/TreeContentAnimation.cpp @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/log.h> +#include <gui/Surface.h> +#include <ui/PixelFormat.h> + +#include <AnimationContext.h> +#include <DisplayListCanvas.h> +#include <RenderNode.h> +#include <renderthread/RenderProxy.h> +#include <renderthread/RenderTask.h> + +#include "Benchmark.h" +#include "TestContext.h" + +#include "protos/hwui.pb.h" + +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <vector> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +class ContextFactory : public IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { + return new AnimationContext(clock); + } +}; + +static DisplayListCanvas* startRecording(RenderNode* node) { + DisplayListCanvas* renderer = new DisplayListCanvas( + node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); + return renderer; +} + +static void endRecording(DisplayListCanvas* renderer, RenderNode* node) { + node->setStagingDisplayList(renderer->finishRecording()); + delete renderer; +} + +class TreeContentAnimation { +public: + virtual ~TreeContentAnimation() {} + int frameCount = 150; + virtual int getFrameCount() { return frameCount; } + virtual void setFrameCount(int fc) { + if (fc > 0) { + frameCount = fc; + } + } + virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0; + virtual void doFrame(int frameNr) = 0; + + template <class T> + static void run(const BenchmarkOptions& opts) { + // Switch to the real display + gDisplay = getBuiltInDisplay(); + + T animation; + animation.setFrameCount(opts.count); + + TestContext testContext; + + // create the native surface + const int width = gDisplay.w; + const int height = gDisplay.h; + sp<Surface> surface = testContext.surface(); + + RenderNode* rootNode = new RenderNode(); + rootNode->incStrong(nullptr); + rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height); + rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + rootNode->mutateStagingProperties().setClipToBounds(false); + rootNode->setPropertyFieldsDirty(RenderNode::GENERIC); + + ContextFactory factory; + std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory)); + proxy->loadSystemProperties(); + proxy->initialize(surface); + float lightX = width / 2.0; + proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); + proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); + + android::uirenderer::Rect DUMMY; + + DisplayListCanvas* renderer = startRecording(rootNode); + animation.createContent(width, height, renderer); + endRecording(renderer, rootNode); + + // Do a few cold runs then reset the stats so that the caches are all hot + for (int i = 0; i < 3; i++) { + testContext.waitForVsync(); + proxy->syncAndDrawFrame(); + } + proxy->resetProfileInfo(); + + for (int i = 0; i < animation.getFrameCount(); i++) { + testContext.waitForVsync(); + + ATRACE_NAME("UI-Draw Frame"); + nsecs_t vsync = systemTime(CLOCK_MONOTONIC); + UiFrameInfoBuilder(proxy->frameInfo()) + .setVsync(vsync, vsync); + animation.doFrame(i); + proxy->syncAndDrawFrame(); + } + + proxy->dumpProfileInfo(STDOUT_FILENO, 0); + rootNode->decStrong(nullptr); + } +}; + +class ShadowGridAnimation : public TreeContentAnimation { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + renderer->insertReorderBarrier(true); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); + renderer->drawRenderNode(card.get()); + cards.push_back(card); + } + } + + renderer->insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->mutateStagingProperties().setElevation(dp(16)); + node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1); + node->mutateStagingProperties().mutableOutline().setShouldClip(true); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); + + DisplayListCanvas* renderer = startRecording(node.get()); + renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _ShadowGrid(BenchmarkInfo{ + "shadowgrid", + "A grid of rounded rects that cast a shadow. Simplified scenario of an " + "Android TV-style launcher interface. High CPU/GPU load.", + TreeContentAnimation::run<ShadowGridAnimation> +}); + +class ShadowGrid2Animation : public TreeContentAnimation { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + renderer->insertReorderBarrier(true); + + for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { + for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { + sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); + renderer->drawRenderNode(card.get()); + cards.push_back(card); + } + } + + renderer->insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->mutateStagingProperties().setElevation(dp(16)); + node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); + node->mutateStagingProperties().mutableOutline().setShouldClip(true); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); + + DisplayListCanvas* renderer = startRecording(node.get()); + renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _ShadowGrid2(BenchmarkInfo{ + "shadowgrid2", + "A dense grid of rounded rects that cast a shadow. This is a higher CPU load " + "variant of shadowgrid. Very high CPU load, high GPU load.", + TreeContentAnimation::run<ShadowGrid2Animation> +}); + +class RectGridAnimation : public TreeContentAnimation { +public: + sp<RenderNode> card; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + renderer->insertReorderBarrier(true); + + card = createCard(40, 40, 200, 200); + renderer->drawRenderNode(card.get()); + + renderer->insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + DisplayListCanvas* renderer = startRecording(node.get()); + renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); + + SkRegion region; + for (int xOffset = 0; xOffset < width; xOffset+=2) { + for (int yOffset = 0; yOffset < height; yOffset+=2) { + region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); + } + } + + SkPaint paint; + paint.setColor(0xff00ffff); + renderer->drawRegion(region, paint); + + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _RectGrid(BenchmarkInfo{ + "rectgrid", + "A dense grid of 1x1 rects that should visually look like a single rect. " + "Low CPU/GPU load.", + TreeContentAnimation::run<RectGridAnimation> +}); + +class OvalAnimation : public TreeContentAnimation { +public: + sp<RenderNode> card; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + renderer->insertReorderBarrier(true); + + card = createCard(40, 40, 400, 400); + renderer->drawRenderNode(card.get()); + + renderer->insertReorderBarrier(false); + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + DisplayListCanvas* renderer = startRecording(node.get()); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(0xFF000000); + renderer->drawOval(0, 0, width, height, paint); + + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _Oval(BenchmarkInfo{ + "oval", + "Draws 1 oval.", + TreeContentAnimation::run<OvalAnimation> +}); + +class PartialDamageTest : public TreeContentAnimation { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + static SkColor COLORS[] = { + 0xFFF44336, + 0xFF9C27B0, + 0xFF2196F3, + 0xFF4CAF50, + }; + + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + sp<RenderNode> card = createCard(x, y, dp(100), dp(100), + COLORS[static_cast<int>((y / dp(116))) % 4]); + renderer->drawRenderNode(card.get()); + cards.push_back(card); + } + } + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + cards[0]->mutateStagingProperties().setTranslationX(curFrame); + cards[0]->mutateStagingProperties().setTranslationY(curFrame); + cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + DisplayListCanvas* renderer = startRecording(cards[0].get()); + renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), + SkXfermode::kSrcOver_Mode); + endRecording(renderer, cards[0].get()); + } + + static SkColor interpolateColor(float fraction, SkColor start, SkColor end) { + int startA = (start >> 24) & 0xff; + int startR = (start >> 16) & 0xff; + int startG = (start >> 8) & 0xff; + int startB = start & 0xff; + + int endA = (end >> 24) & 0xff; + int endR = (end >> 16) & 0xff; + int endG = (end >> 8) & 0xff; + int endB = end & 0xff; + + return (int)((startA + (int)(fraction * (endA - startA))) << 24) | + (int)((startR + (int)(fraction * (endR - startR))) << 16) | + (int)((startG + (int)(fraction * (endG - startG))) << 8) | + (int)((startB + (int)(fraction * (endB - startB)))); + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + DisplayListCanvas* renderer = startRecording(node.get()); + renderer->drawColor(color, SkXfermode::kSrcOver_Mode); + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _PartialDamage(BenchmarkInfo{ + "partialdamage", + "Tests the partial invalidation path. Draws a grid of rects and animates 1 " + "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or " + "EGL_KHR_partial_update is supported by the device & are enabled in hwui.", + TreeContentAnimation::run<PartialDamageTest> +}); diff --git a/libs/hwui/tests/how_to_run.txt b/libs/hwui/tests/how_to_run.txt index 85900eff2f78..b051768f3262 100644 --- a/libs/hwui/tests/how_to_run.txt +++ b/libs/hwui/tests/how_to_run.txt @@ -2,16 +2,4 @@ mmm -j8 frameworks/base/libs/hwui/ && adb push $OUT/data/local/tmp/hwuitest /data/local/tmp/hwuitest && adb shell /data/local/tmp/hwuitest - -Command arguments: -hwuitest [testname] - -Default test is 'shadowgrid' - -List of tests: - -shadowgrid: creates a grid of rounded rects that cast shadows, high CPU & GPU load - -rectgrid: creates a grid of 1x1 rects - -oval: draws 1 oval +Pass --help to get help diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp index 0bbf08c76c90..aee84de3ae7b 100644 --- a/libs/hwui/tests/main.cpp +++ b/libs/hwui/tests/main.cpp @@ -14,385 +14,181 @@ * limitations under the License. */ -#include <cutils/log.h> -#include <gui/Surface.h> -#include <ui/PixelFormat.h> - -#include <AnimationContext.h> -#include <DisplayListCanvas.h> -#include <RenderNode.h> -#include <renderthread/RenderProxy.h> -#include <renderthread/RenderTask.h> - -#include "TestContext.h" +#include "Benchmark.h" #include "protos/hwui.pb.h" +#include <getopt.h> #include <stdio.h> +#include <string> #include <unistd.h> +#include <unordered_map> +#include <vector> using namespace android; using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; -using namespace android::uirenderer::test; - -class ContextFactory : public IContextFactory { -public: - virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { - return new AnimationContext(clock); - } -}; -static DisplayListCanvas* startRecording(RenderNode* node) { - DisplayListCanvas* renderer = new DisplayListCanvas( - node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); - return renderer; +// Not a static global because we need to force the map to be constructed +// before we try to add things to it. +std::unordered_map<std::string, BenchmarkInfo>& testMap() { + static std::unordered_map<std::string, BenchmarkInfo> testMap; + return testMap; } -static void endRecording(DisplayListCanvas* renderer, RenderNode* node) { - node->setStagingDisplayList(renderer->finishRecording()); - delete renderer; +void Benchmark::registerBenchmark(const BenchmarkInfo& info) { + testMap()[info.name] = info; } -class TreeContentAnimation { -public: - virtual ~TreeContentAnimation() {} - int frameCount = 150; - virtual int getFrameCount() { return frameCount; } - virtual void setFrameCount(int fc) { - if (fc > 0) { - frameCount = fc; - } - } - virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0; - virtual void doFrame(int frameNr) = 0; - - template <class T> - static void run(int frameCount) { - T animation; - animation.setFrameCount(frameCount); - - TestContext testContext; - - // create the native surface - const int width = gDisplay.w; - const int height = gDisplay.h; - sp<Surface> surface = testContext.surface(); - - RenderNode* rootNode = new RenderNode(); - rootNode->incStrong(nullptr); - rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height); - rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - rootNode->mutateStagingProperties().setClipToBounds(false); - rootNode->setPropertyFieldsDirty(RenderNode::GENERIC); - - ContextFactory factory; - std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory)); - proxy->loadSystemProperties(); - proxy->initialize(surface); - float lightX = width / 2.0; - proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); - proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); - - android::uirenderer::Rect DUMMY; - - DisplayListCanvas* renderer = startRecording(rootNode); - animation.createContent(width, height, renderer); - endRecording(renderer, rootNode); - - // Do a few cold runs then reset the stats so that the caches are all hot - for (int i = 0; i < 3; i++) { - testContext.waitForVsync(); - proxy->syncAndDrawFrame(); - } - proxy->resetProfileInfo(); - - for (int i = 0; i < animation.getFrameCount(); i++) { - testContext.waitForVsync(); - - ATRACE_NAME("UI-Draw Frame"); - nsecs_t vsync = systemTime(CLOCK_MONOTONIC); - UiFrameInfoBuilder(proxy->frameInfo()) - .setVsync(vsync, vsync); - animation.doFrame(i); - proxy->syncAndDrawFrame(); - } - - proxy->dumpProfileInfo(STDOUT_FILENO, 0); - rootNode->decStrong(nullptr); - } -}; - -class ShadowGridAnimation : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); +static int gFrameCount = 150; +static int gRepeatCount = 1; +static std::vector<BenchmarkInfo> gRunTests; + +static void printHelp() { + printf("\ +USAGE: hwuitest [OPTIONS] <TESTNAME>\n\ +\n\ +OPTIONS:\n\ + -c, --count=NUM NUM loops a test should run (example, number of frames)\n\ + -r, --runs=NUM Repeat the test(s) NUM times\n\ + -h, --help Display this help\n\ + --list List all tests\n\ +\n"); +} - for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { - for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { - sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); - renderer->drawRenderNode(card.get()); - cards.push_back(card); +static void listTests() { + printf("Tests: \n"); + for (auto&& test : testMap()) { + auto&& info = test.second; + const char* col1 = info.name.c_str(); + int dlen = info.description.length(); + const char* col2 = info.description.c_str(); + // World's best line breaking algorithm. + do { + int toPrint = dlen; + if (toPrint > 50) { + char* found = (char*) memrchr(col2, ' ', 50); + if (found) { + toPrint = found - col2; + } else { + toPrint = 50; + } } - } - - renderer->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(curFrame); - cards[ci]->mutateStagingProperties().setTranslationY(curFrame); - cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->mutateStagingProperties().setElevation(dp(16)); - node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1); - node->mutateStagingProperties().mutableOutline().setShouldClip(true); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); - return node; - } -}; - -class ShadowGrid2Animation : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { - for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { - sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); - renderer->drawRenderNode(card.get()); - cards.push_back(card); + printf("%-20s %.*s\n", col1, toPrint, col2); + col1 = ""; + col2 += toPrint; + dlen -= toPrint; + while (*col2 == ' ') { + col2++; dlen--; } - } - - renderer->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(curFrame); - cards[ci]->mutateStagingProperties().setTranslationY(curFrame); - cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->mutateStagingProperties().setElevation(dp(16)); - node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); - node->mutateStagingProperties().mutableOutline().setShouldClip(true); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); - return node; - } -}; - -class RectGridAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - card = createCard(40, 40, 200, 200); - renderer->drawRenderNode(card.get()); - - renderer->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } while (dlen > 0); + printf("\n"); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); - - SkRegion region; - for (int xOffset = 0; xOffset < width; xOffset+=2) { - for (int yOffset = 0; yOffset < height; yOffset+=2) { - region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); - } - } - - SkPaint paint; - paint.setColor(0xff00ffff); - renderer->drawRegion(region, paint); +} - endRecording(renderer, node.get()); - return node; - } +static const struct option LONG_OPTIONS[] = { + { "frames", required_argument, nullptr, 'f' }, + { "repeat", required_argument, nullptr, 'r' }, + { "help", no_argument, nullptr, 'h' }, + { "list", no_argument, nullptr, 'l' }, + { 0, 0, 0, 0 } }; -class OvalAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - card = createCard(40, 40, 400, 400); - renderer->drawRenderNode(card.get()); +static const char* SHORT_OPTIONS = "c:r:h"; - renderer->insertReorderBarrier(false); - } +void parseOptions(int argc, char* argv[]) { + int c; + // temporary variable + int count; + bool error = false; + opterr = 0; - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + while (true) { - DisplayListCanvas* renderer = startRecording(node.get()); + /* getopt_long stores the option index here. */ + int option_index = 0; - SkPaint paint; - paint.setAntiAlias(true); - paint.setColor(0xFF000000); - renderer->drawOval(0, 0, width, height, paint); + c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index); - endRecording(renderer, node.get()); - return node; - } -}; + if (c == -1) + break; -class PartialInvalTest : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - static SkColor COLORS[] = { - 0xFFF44336, - 0xFF9C27B0, - 0xFF2196F3, - 0xFF4CAF50, - }; + switch (c) { + case 0: + // Option set a flag, don't need to do anything + // (although none of the current LONG_OPTIONS do this...) + break; - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + case 'l': + listTests(); + exit(EXIT_SUCCESS); + break; - for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { - for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { - sp<RenderNode> card = createCard(x, y, dp(100), dp(100), - COLORS[static_cast<int>((y / dp(116))) % 4]); - renderer->drawRenderNode(card.get()); - cards.push_back(card); + case 'c': + count = atoi(optarg); + if (!count) { + fprintf(stderr, "Invalid frames argument '%s'\n", optarg); + error = true; + } else { + gFrameCount = (count > 0 ? count : INT_MAX); + } + break; + + case 'r': + count = atoi(optarg); + if (!count) { + fprintf(stderr, "Invalid repeat argument '%s'\n", optarg); + error = true; + } else { + gRepeatCount = (count > 0 ? count : INT_MAX); } + break; + + case 'h': + printHelp(); + exit(EXIT_SUCCESS); + break; + + case '?': + fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]); + // fall-through + default: + error = true; + break; } } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - cards[0]->mutateStagingProperties().setTranslationX(curFrame); - cards[0]->mutateStagingProperties().setTranslationY(curFrame); - cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(cards[0].get()); - renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), - SkXfermode::kSrcOver_Mode); - endRecording(renderer, cards[0].get()); + if (error) { + fprintf(stderr, "Try 'hwuitest --help' for more information.\n"); + exit(EXIT_FAILURE); } - static SkColor interpolateColor(float fraction, SkColor start, SkColor end) { - int startA = (start >> 24) & 0xff; - int startR = (start >> 16) & 0xff; - int startG = (start >> 8) & 0xff; - int startB = start & 0xff; - - int endA = (end >> 24) & 0xff; - int endR = (end >> 16) & 0xff; - int endG = (end >> 8) & 0xff; - int endB = end & 0xff; - - return (int)((startA + (int)(fraction * (endA - startA))) << 24) | - (int)((startR + (int)(fraction * (endR - startR))) << 16) | - (int)((startG + (int)(fraction * (endG - startG))) << 8) | - (int)((startB + (int)(fraction * (endB - startB)))); - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(color, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); - return node; - } -}; - -struct cstr_cmp { - bool operator()(const char *a, const char *b) const { - return std::strcmp(a, b) < 0; + /* Print any remaining command line arguments (not options). */ + if (optind < argc) { + do { + const char* test = argv[optind++]; + auto pos = testMap().find(test); + if (pos == testMap().end()) { + fprintf(stderr, "Unknown test '%s'\n", test); + exit(EXIT_FAILURE); + } else { + gRunTests.push_back(pos->second); + } + } while (optind < argc); + } else { + gRunTests.push_back(testMap()["shadowgrid"]); } -}; - -typedef void (*testProc)(int); - -std::map<const char*, testProc, cstr_cmp> gTestMap { - {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>}, - {"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>}, - {"rectgrid", TreeContentAnimation::run<RectGridAnimation> }, - {"oval", TreeContentAnimation::run<OvalAnimation> }, - {"partialinval", TreeContentAnimation::run<PartialInvalTest> }, -}; +} int main(int argc, char* argv[]) { - const char* testName = argc > 1 ? argv[1] : "shadowgrid"; - testProc proc = gTestMap[testName]; - if(!proc) { - printf("Error: couldn't find test %s\n", testName); - return 1; - } - int loopCount = 1; - if (argc > 2) { - loopCount = atoi(argv[2]); - if (!loopCount) { - printf("Invalid loop count!\n"); - return 1; - } - } - int frameCount = 150; - if (argc > 3) { - frameCount = atoi(argv[3]); - if (frameCount < 1) { - printf("Invalid frame count!\n"); - return 1; + parseOptions(argc, argv); + + BenchmarkOptions opts; + opts.count = gFrameCount; + for (int i = 0; i < gRepeatCount; i++) { + for (auto&& test : gRunTests) { + test.functor(opts); } } - if (loopCount < 0) { - loopCount = INT_MAX; - } - for (int i = 0; i < loopCount; i++) { - proc(frameCount); - } printf("Success!\n"); return 0; } diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index ba02f5f1a77d..d00236ed955a 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -16,12 +16,19 @@ #ifndef PAINT_UTILS_H #define PAINT_UTILS_H +#include <utils/Blur.h> + #include <SkColorFilter.h> +#include <SkDrawLooper.h> #include <SkXfermode.h> namespace android { namespace uirenderer { +/** + * Utility methods for accessing data within SkPaint, and providing defaults + * with optional SkPaint pointers. + */ class PaintUtils { public: @@ -73,6 +80,39 @@ public: return (filter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag) == 0; } + struct TextShadow { + SkScalar radius; + float dx; + float dy; + SkColor color; + }; + + static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) { + SkDrawLooper::BlurShadowRec blur; + if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) { + if (textShadow) { + textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma); + textShadow->dx = blur.fOffset.fX; + textShadow->dy = blur.fOffset.fY; + textShadow->color = blur.fColor; + } + return true; + } + return false; + } + + static inline bool hasTextShadow(const SkPaint* paint) { + return getTextShadow(paint, nullptr); + } + + static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) { + return paint ? getXfermode(paint->getXfermode()) : SkXfermode::kSrcOver_Mode; + } + + static inline int getAlphaDirect(const SkPaint* paint) { + return paint ? paint->getAlpha() : 255; + } + }; // class PaintUtils } /* namespace uirenderer */ diff --git a/libs/hwui/utils/TinyHashMap.h b/libs/hwui/utils/TinyHashMap.h deleted file mode 100644 index 4ff9a42f6d5d..000000000000 --- a/libs/hwui/utils/TinyHashMap.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_TINYHASHMAP_H -#define ANDROID_HWUI_TINYHASHMAP_H - -#include <utils/BasicHashtable.h> - -namespace android { -namespace uirenderer { - -/** - * A very simple hash map that doesn't allow duplicate keys, overwriting the older entry. - */ -template <typename TKey, typename TValue> -class TinyHashMap { -public: - typedef key_value_pair_t<TKey, TValue> TEntry; - - /** - * Puts an entry in the hash, removing any existing entry with the same key - */ - void put(TKey key, TValue value) { - hash_t hash = android::hash_type(key); - - ssize_t index = mTable.find(-1, hash, key); - if (index != -1) { - mTable.removeAt(index); - } - - TEntry initEntry(key, value); - mTable.add(hash, initEntry); - } - - /** - * Return true if key is in the map, in which case stores the value in the output ref - */ - bool get(TKey key, TValue& outValue) { - hash_t hash = android::hash_type(key); - ssize_t index = mTable.find(-1, hash, key); - if (index == -1) { - return false; - } - outValue = mTable.entryAt(index).value; - return true; - } - - void clear() { mTable.clear(); } - -private: - BasicHashtable<TKey, TEntry> mTable; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TINYHASHMAP_H |