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

Commit 3a15f392 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Cache VectorDrawables in an atlas"

parents af11a49d 3310fb1b
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