1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <map>
20#include <SkSurface.h>
21#include <utils/FatVector.h>
22#include <utils/RefBase.h>
23#include <list>
24
25class GrRectanizer;
26
27namespace android {
28namespace uirenderer {
29namespace skiapipeline {
30
31typedef uintptr_t AtlasKey;
32
33#define INVALID_ATLAS_KEY 0
34
35struct AtlasEntry {
36    sk_sp<SkSurface> surface;
37    SkRect rect;
38    AtlasKey key = INVALID_ATLAS_KEY;
39};
40
41/**
42 * VectorDrawableAtlas provides offscreen buffers used to draw VD and AnimatedVD.
43 * VectorDrawableAtlas can allocate a standalone surface or provide a subrect from a shared surface.
44 * VectorDrawableAtlas is owned by the CacheManager and weak pointers are kept by each
45 * VectorDrawable that is using it. VectorDrawableAtlas and its surface can be deleted at any time,
46 * except during a renderFrame call. VectorDrawable does not contain a pointer to atlas SkSurface
47 * nor any coordinates into the atlas, but instead holds a rectangle "id", which is resolved only
48 * when drawing. This design makes VectorDrawableAtlas free to move the data internally.
49 * At draw time a VectorDrawable may find, that its atlas has been deleted, which will make it
50 * draw in a standalone cache surface not part of an atlas. In this case VD won't use
51 * VectorDrawableAtlas until the next frame.
52 * VectorDrawableAtlas tries to fit VDs in the atlas SkSurface. If there is not enough space in
53 * the atlas, VectorDrawableAtlas creates a standalone surface for each VD.
54 * When a VectorDrawable is deleted, it invokes VectorDrawableAtlas::releaseEntry, which is keeping
55 * track of free spaces and allow to reuse the surface for another VD.
56 */
57 //TODO: Check if not using atlas for AnimatedVD is more efficient.
58 //TODO: For low memory situations, when there are no paint effects in VD, we may render without an
59 //TODO: offscreen surface.
60class VectorDrawableAtlas : public virtual RefBase {
61public:
62    enum class StorageMode {
63        allowSharedSurface,
64        disallowSharedSurface
65    };
66
67    VectorDrawableAtlas(size_t surfaceArea,
68            StorageMode storageMode = StorageMode::allowSharedSurface);
69
70    /**
71     * "prepareForDraw" may allocate a new surface if needed. It may schedule to repack the
72     * atlas at a later time.
73     */
74    void prepareForDraw(GrContext* context);
75
76    /**
77     * Repack the atlas if needed, by moving used rectangles into a new atlas surface.
78     * The goal of repacking is to fix a fragmented atlas.
79     */
80    void repackIfNeeded(GrContext* context);
81
82    /**
83     * Returns true if atlas is fragmented and repack is needed.
84     */
85    bool isFragmented();
86
87    /**
88     * "requestNewEntry" is called by VectorDrawable to allocate a new rectangle area from the atlas
89     * or create a standalone surface if atlas is full.
90     * On success it returns a non-negative unique id, which can be used later with "getEntry" and
91     * "releaseEntry".
92     */
93    AtlasEntry requestNewEntry(int width, int height, GrContext* context);
94
95    /**
96     * "getEntry" extracts coordinates and surface of a previously created rectangle.
97     * "atlasKey" is an unique id created by "requestNewEntry". Passing a non-existing "atlasKey" is
98     * causing an undefined behaviour.
99     * On success it returns a rectangle Id -> may be same or different from "atlasKey" if
100     * implementation decides to move the record internally.
101     */
102    AtlasEntry getEntry(AtlasKey atlasKey);
103
104    /**
105     * "releaseEntry" is invoked when a VectorDrawable is deleted. Passing a non-existing "atlasKey"
106     * is causing an undefined behaviour.
107     */
108    void releaseEntry(AtlasKey atlasKey);
109
110    void setStorageMode(StorageMode mode);
111
112private:
113    struct CacheEntry {
114        CacheEntry(const SkRect& newVDrect, const SkRect& newRect,
115                const sk_sp<SkSurface>& newSurface)
116                : VDrect(newVDrect)
117                , rect(newRect)
118                , surface(newSurface) { }
119
120        /**
121         * size and position of VectorDrawable into the atlas or in "this.surface"
122         */
123        SkRect VDrect;
124
125        /**
126         * rect allocated in atlas surface or "this.surface". It may be bigger than "VDrect"
127         */
128        SkRect rect;
129
130        /**
131         * this surface is used if atlas is full or VD is too big
132         */
133        sk_sp<SkSurface> surface;
134
135        /**
136         * iterator is used to delete self with a constant complexity (without traversing the list)
137         */
138        std::list<CacheEntry>::iterator eraseIt;
139    };
140
141    /**
142     * atlas surface shared by all VDs
143     */
144    sk_sp<SkSurface> mSurface;
145
146    std::unique_ptr<GrRectanizer> mRectanizer;
147    const int mWidth;
148    const int mHeight;
149
150    /**
151     * "mRects" keeps records only for rectangles used by VDs. List has nice properties: constant
152     * complexity to insert and erase and references are not invalidated by insert/erase.
153     */
154    std::list<CacheEntry> mRects;
155
156    /**
157     * Rectangles freed by "releaseEntry" are removed from "mRects" and added to "mFreeRects".
158     * "mFreeRects" is using for an index the rectangle area. There could be more than one free
159     * rectangle with the same area, which is the reason to use "multimap" instead of "map".
160     */
161    std::multimap<size_t, SkRect> mFreeRects;
162
163    /**
164     * area in atlas used by VectorDrawables (area in standalone surface not counted)
165     */
166    int mPixelUsedByVDs = 0;
167
168    /**
169     * area allocated in mRectanizer
170     */
171    int mPixelAllocated = 0;
172
173    /**
174     * Consecutive times we had to allocate standalone surfaces, because atlas was full.
175     */
176    int mConsecutiveFailures = 0;
177
178    /**
179     * mStorageMode allows using a shared surface to store small vector drawables.
180     * Using a shared surface can boost the performance by allowing GL ops to be batched, but may
181     * consume more memory.
182     */
183    StorageMode mStorageMode;
184
185    sk_sp<SkSurface> createSurface(int width, int height, GrContext* context);
186
187    inline bool fitInAtlas(int width, int height) {
188        return 2*width < mWidth && 2*height < mHeight;
189    }
190
191    void repack(GrContext* context);
192
193    static bool compareCacheEntry(const CacheEntry& first, const CacheEntry& second);
194};
195
196} /* namespace skiapipeline */
197} /* namespace uirenderer */
198} /* namespace android */
199