Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3310fb1b authored by Stan Iliev's avatar Stan Iliev
Browse files

Cache VectorDrawables in an atlas

Optimize VectorDrawables for Skia pipeline: draw small VectorDrawables
in a GPU atlas instead of seprate offscreen buffers.
This implementation is using CacheManger and allows for the atlas to
be released if there is a memory pressure.

Test: A new unit test for VectorDrawableAtlas is passing. Systrace shows
0.5ms faster DrawFrame for fling in Settings app main screen.
Change-Id: Ide3884eefae777e1547f1dfdb67b807185839fb4
parent a554ba6e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ cc_defaults {
        "external/skia/src/effects",
        "external/skia/src/image",
        "external/skia/src/utils",
        "external/skia/src/gpu",
    ],

    product_variables: {
@@ -133,6 +134,7 @@ cc_defaults {
        "pipeline/skia/SkiaProfileRenderer.cpp",
        "pipeline/skia/SkiaRecordingCanvas.cpp",
        "pipeline/skia/SkiaVulkanPipeline.cpp",
        "pipeline/skia/VectorDrawableAtlas.cpp",
        "renderstate/Blend.cpp",
        "renderstate/MeshState.cpp",
        "renderstate/OffscreenBufferPool.cpp",
@@ -340,6 +342,7 @@ cc_test {
        "tests/unit/TextureCacheTests.cpp",
	"tests/unit/TypefaceTests.cpp",
        "tests/unit/VectorDrawableTests.cpp",
        "tests/unit/VectorDrawableAtlasTests.cpp",
    ],
}

+89 −20
Original line number Diff line number Diff line
@@ -491,34 +491,103 @@ Bitmap& Tree::getBitmapUpdateIfDirty() {
    return *mCache.bitmap;
}

void Tree::updateCache(sk_sp<SkSurface> surface) {
    if (surface.get()) {
        mCache.surface = surface;
    }
    if (surface.get() || mCache.dirty) {
        SkSurface* vdSurface = mCache.surface.get();
        SkCanvas* canvas = vdSurface->getCanvas();
        float scaleX = vdSurface->width() / mProperties.getViewportWidth();
        float scaleY = vdSurface->height() / mProperties.getViewportHeight();
void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context) {
    SkRect dst;
    sk_sp<SkSurface> surface = mCache.getSurface(&dst);
    bool canReuseSurface = surface && dst.width() >= mProperties.getScaledWidth()
            && dst.height() >= mProperties.getScaledHeight();
    if (!canReuseSurface) {
        int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
        int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
        auto atlasEntry = atlas->requestNewEntry(scaledWidth, scaledHeight, context);
        if (INVALID_ATLAS_KEY != atlasEntry.key) {
            dst = atlasEntry.rect;
            surface = atlasEntry.surface;
            mCache.setAtlas(atlas, atlasEntry.key);
        } else {
            //don't draw, if we failed to allocate an offscreen buffer
            mCache.clear();
            surface.reset();
        }
    }
    if (!canReuseSurface || mCache.dirty) {
        draw(surface.get(), dst);
        mCache.dirty = false;
    }
}

void Tree::draw(SkSurface* surface, const SkRect& dst) {
    if (surface) {
        SkCanvas* canvas = surface->getCanvas();
        float scaleX = dst.width() / mProperties.getViewportWidth();
        float scaleY = dst.height() / mProperties.getViewportHeight();
        SkAutoCanvasRestore acr(canvas, true);
        canvas->translate(dst.fLeft, dst.fTop);
        canvas->clipRect(SkRect::MakeWH(dst.width(), dst.height()));
        canvas->clear(SK_ColorTRANSPARENT);
        canvas->scale(scaleX, scaleY);
        mRootNode->draw(canvas, false);
        mCache.dirty = false;
        canvas->flush();
    }
}

void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas,
        skiapipeline::AtlasKey newAtlasKey) {
    LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY);
    clear();
    mAtlas = newAtlas;
    mAtlasKey = newAtlasKey;
}

sk_sp<SkSurface> Tree::Cache::getSurface(SkRect* bounds) {
    sk_sp<SkSurface> surface;
    sp<skiapipeline::VectorDrawableAtlas> atlas = mAtlas.promote();
    if (atlas.get() && mAtlasKey != INVALID_ATLAS_KEY) {
        auto atlasEntry = atlas->getEntry(mAtlasKey);
        *bounds = atlasEntry.rect;
        surface = atlasEntry.surface;
        mAtlasKey = atlasEntry.key;
    }

    return surface;
}

void Tree::Cache::clear() {
    sp<skiapipeline::VectorDrawableAtlas> lockAtlas = mAtlas.promote();
    if (lockAtlas.get()) {
        lockAtlas->releaseEntry(mAtlasKey);
    }
    mAtlas = nullptr;
    mAtlasKey = INVALID_ATLAS_KEY;
}

void Tree::draw(SkCanvas* canvas) {
   /*
    * TODO address the following...
    *
    * 1) figure out how to set path's as volatile during animation
    * 2) if mRoot->getPaint() != null either promote to layer (during
    *    animation) or cache in SkSurface (for static content)
    */
    canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(),
    SkRect src;
    sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
    if (vdSurface) {
        canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src,
                mutateProperties()->getBounds(), getPaint());
    } else {
        // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure.
        // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next
        // frame will be cached into the atlas.
        int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
        int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
        SkRect src = SkRect::MakeWH(scaledWidth, scaledHeight);
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
        sk_sp<SkColorSpace> colorSpace = nullptr;
#else
        sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
#endif
        SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType,
                colorSpace);
        sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(canvas->getGrContext(),
                SkBudgeted::kYes, info);
        draw(surface.get(), src);
        mCache.clear();
        canvas->drawImageRect(surface->makeImageSnapshot().get(), mutateProperties()->getBounds(),
                getPaint());
        markDirty();
    }
}

void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
+43 −16
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include "hwui/Canvas.h"
#include "hwui/Bitmap.h"
#include "renderthread/CacheManager.h"
#include "DisplayList.h"

#include <SkBitmap.h>
@@ -687,35 +688,61 @@ public:
    bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; }
    void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }

    // Returns true if VD cache surface is big enough. This should always be called from RT and it
    // works with Skia pipelines only.
    bool canReuseSurface() {
        SkSurface* surface = mCache.surface.get();
        return surface && surface->width() >= mProperties.getScaledWidth()
              && surface->height() >= mProperties.getScaledHeight();
    }

    // Draws VD cache into a canvas. This should always be called from RT and it works with Skia
    // pipelines only.
    /**
     * Draws VD cache into a canvas. This should always be called from RT and it works with Skia
     * pipelines only.
     */
    void draw(SkCanvas* canvas);

    // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must
    // contain a new surface. This should always be called from RT and it works with Skia pipelines
    // only.
    void updateCache(sk_sp<SkSurface> surface);
    /**
     * Draws VD into a GPU backed surface.
     * This should always be called from RT and it works with Skia pipeline only.
     */
    void updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context);

private:
    struct Cache {
    class Cache {
    public:
        sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software
        //TODO: use surface instead of bitmap when drawing in software canvas
        sk_sp<SkSurface> surface; //used only by Skia pipelines
        bool dirty = true;

        // the rest of the code in Cache is used by Skia pipelines only

        ~Cache() { clear(); }

        /**
         * Stores a weak pointer to the atlas and a key.
         */
        void setAtlas(sp<skiapipeline::VectorDrawableAtlas> atlas,
                skiapipeline::AtlasKey newAtlasKey);

        /**
         * Gets a surface and bounds from the atlas.
         *
         * @return nullptr if the altas has been deleted.
         */
        sk_sp<SkSurface> getSurface(SkRect* bounds);

        /**
         * Releases atlas key from the atlas, which makes it available for reuse.
         */
        void clear();
    private:
        wp<skiapipeline::VectorDrawableAtlas> mAtlas;
        skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY;
    };

    SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop);
    bool allocateBitmapIfNeeded(Cache& cache, int width, int height);
    bool canReuseBitmap(Bitmap*, int width, int height);
    void updateBitmapCache(Bitmap& outCache, bool useStagingData);

    /**
     * Draws the root node into "surface" at a given "dst" position.
     */
    void draw(SkSurface* surface, const SkRect& dst);

    // Cap the bitmap size, such that it won't hurt the performance too much
    // and it won't crash due to a very large scale.
    // The drawable will look blurry above this size.
+8 −18
Original line number Diff line number Diff line
@@ -183,27 +183,17 @@ public:
};

void SkiaPipeline::renderVectorDrawableCache() {
    //render VectorDrawables into offscreen buffers
    if (!mVectorDrawables.empty()) {
        sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas();
        auto grContext = mRenderThread.getGrContext();
        atlas->prepareForDraw(grContext);
        for (auto vd : mVectorDrawables) {
        sk_sp<SkSurface> surface;
        if (!vd->canReuseSurface()) {
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
            sk_sp<SkColorSpace> colorSpace = nullptr;
#else
            sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
#endif
            int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth());
            int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight());
            SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight,
                    kPremul_SkAlphaType, colorSpace);
            SkASSERT(mRenderThread.getGrContext() != nullptr);
            surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
                    info);
        }
        vd->updateCache(surface);
            vd->updateCache(atlas, grContext);
        }
        grContext->flush();
        mVectorDrawables.clear();
    }
}

void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
        const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
+269 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 "VectorDrawableAtlas.h"

#include <GrRectanizer_pow2.h>
#include <SkCanvas.h>
#include <cmath>
#include "utils/TraceUtils.h"
#include "renderthread/RenderProxy.h"

namespace android {
namespace uirenderer {
namespace skiapipeline {

VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode)
        : mWidth((int)std::sqrt(surfaceArea))
        , mHeight((int)std::sqrt(surfaceArea))
        , mStorageMode(storageMode) {
}

void VectorDrawableAtlas::prepareForDraw(GrContext* context) {
    if (StorageMode::allowSharedSurface == mStorageMode) {
        if (!mSurface) {
            mSurface = createSurface(mWidth, mHeight, context);
            mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight);
            mPixelUsedByVDs = 0;
            mPixelAllocated = 0;
            mConsecutiveFailures = 0;
            mFreeRects.clear();
        } else {
            if (isFragmented()) {
                // Invoke repack outside renderFrame to avoid jank.
                renderthread::RenderProxy::repackVectorDrawableAtlas();
            }
        }
    }
}

#define MAX_CONSECUTIVE_FAILURES 5
#define MAX_UNUSED_RATIO 2.0f

bool VectorDrawableAtlas::isFragmented() {
    return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES
            && mPixelUsedByVDs*MAX_UNUSED_RATIO < mPixelAllocated;
}

void VectorDrawableAtlas::repackIfNeeded(GrContext* context) {
    // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive
    // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels
    // used by atlas VDs.
    if (isFragmented() && mSurface) {
        repack(context);
    }
}

// compare to CacheEntry objects based on VD area.
bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second)
{
    return first.VDrect.width()*first.VDrect.height() < second.VDrect.width()*second.VDrect.height();
}

void VectorDrawableAtlas::repack(GrContext* context) {
    ATRACE_CALL();
    sk_sp<SkSurface> newSurface;
    SkCanvas* canvas = nullptr;
    if (StorageMode::allowSharedSurface == mStorageMode) {
        newSurface = createSurface(mWidth, mHeight, context);
        if (!newSurface) {
            return;
        }
        canvas = newSurface->getCanvas();
        canvas->clear(SK_ColorTRANSPARENT);
        mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight);
    } else {
        if (!mSurface) {
            return; //nothing to repack
        }
        mRectanizer.reset();
    }
    mFreeRects.clear();
    SkImage* sourceImageAtlas = nullptr;
    if (mSurface) {
        sourceImageAtlas = mSurface->makeImageSnapshot().get();
    }

    // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas.
    // Sorting is safe, because it does not affect iterator validity.
    if (mRects.size() <= 100) {
        mRects.sort(compareCacheEntry);
    }

    for (CacheEntry& entry : mRects) {
        SkRect currentVDRect = entry.VDrect;
        SkImage* sourceImage; //copy either from the atlas or from a standalone surface
        if (entry.surface) {
            if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) {
                continue; //don't even try to repack huge VD
            }
            sourceImage = entry.surface->makeImageSnapshot().get();
        } else {
            sourceImage = sourceImageAtlas;
        }
        size_t VDRectArea = currentVDRect.width()*currentVDRect.height();
        SkIPoint16 pos;
        if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) {
            SkRect newRect = SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(),
                    currentVDRect.height());
            canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr);
            entry.VDrect = newRect;
            entry.rect = newRect;
            if (entry.surface) {
                // A rectangle moved from a standalone surface to the atlas.
                entry.surface = nullptr;
                mPixelUsedByVDs += VDRectArea;
            }
        } else {
            // Repack failed for this item. If it is not already, store it in a standalone
            // surface.
            if (!entry.surface) {
                // A rectangle moved from an atlas to a standalone surface.
                mPixelUsedByVDs -= VDRectArea;
                SkRect newRect = SkRect::MakeWH(currentVDRect.width(),
                        currentVDRect.height());
                entry.surface = createSurface(newRect.width(), newRect.height(), context);
                auto tempCanvas = entry.surface->getCanvas();
                tempCanvas->clear(SK_ColorTRANSPARENT);
                tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr);
                entry.VDrect = newRect;
                entry.rect = newRect;
            }
        }
    }
    mPixelAllocated = mPixelUsedByVDs;
    context->flush();
    mSurface = newSurface;
    mConsecutiveFailures = 0;
}

AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) {
    AtlasEntry result;
    if (width <= 0 || height <= 0) {
        return result;
    }

    if (mSurface) {
        const size_t area = width*height;

        // Use a rectanizer to allocate unused space from the atlas surface.
        bool notTooBig = fitInAtlas(width, height);
        SkIPoint16 pos;
        if (notTooBig && mRectanizer->addRect(width, height, &pos)) {
            mPixelUsedByVDs += area;
            mPixelAllocated += area;
            result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height);
            result.surface = mSurface;
            auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr);
            CacheEntry* entry = &(*eraseIt);
            entry->eraseIt = eraseIt;
            result.key = reinterpret_cast<AtlasKey>(entry);
            mConsecutiveFailures = 0;
            return result;
        }

        // Try to reuse atlas memory from rectangles freed by "releaseEntry".
        auto freeRectIt = mFreeRects.lower_bound(area);
        while (freeRectIt != mFreeRects.end()) {
            SkRect& freeRect = freeRectIt->second;
            if (freeRect.width() >= width && freeRect.height() >= height) {
                result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height);
                result.surface = mSurface;
                auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr);
                CacheEntry* entry = &(*eraseIt);
                entry->eraseIt = eraseIt;
                result.key = reinterpret_cast<AtlasKey>(entry);
                mPixelUsedByVDs += area;
                mFreeRects.erase(freeRectIt);
                mConsecutiveFailures = 0;
                return result;
            }
            freeRectIt++;
        }

        if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) {
            mConsecutiveFailures++;
        }
    }

    // Allocate a surface for a rectangle that is too big or if atlas is full.
    if (nullptr != context) {
        result.rect = SkRect::MakeWH(width, height);
        result.surface = createSurface(width, height, context);
        auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface);
        CacheEntry* entry = &(*eraseIt);
        entry->eraseIt = eraseIt;
        result.key = reinterpret_cast<AtlasKey>(entry);
    }

    return result;
}

AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) {
    AtlasEntry result;
    if (INVALID_ATLAS_KEY != atlasKey) {
        CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey);
        result.rect = entry->VDrect;
        result.surface = entry->surface;
        if (!result.surface) {
            result.surface = mSurface;
        }
        result.key = atlasKey;
    }
    return result;
}

void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) {
    if (INVALID_ATLAS_KEY != atlasKey) {
        CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey);
        if (!entry->surface) {
            // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas
            // is full.
            SkRect& removedRect = entry->rect;
            size_t rectArea = removedRect.width()*removedRect.height();
            mFreeRects.emplace(rectArea, removedRect);
            SkRect& removedVDRect = entry->VDrect;
            size_t VDRectArea = removedVDRect.width()*removedVDRect.height();
            mPixelUsedByVDs -= VDRectArea;
            mConsecutiveFailures = 0;
        }
        auto eraseIt = entry->eraseIt;
        mRects.erase(eraseIt);
    }
}

sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) {
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
    sk_sp<SkColorSpace> colorSpace = nullptr;
#else
    sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
#endif
    SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace);
    return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info);
}

void VectorDrawableAtlas::setStorageMode(StorageMode mode) {
    mStorageMode = mode;
    if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) {
        mSurface.reset();
        mRectanizer.reset();
        mFreeRects.clear();
    }
}

} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
Loading