1/*
2 * Copyright (C) 2012 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#ifndef ANDROID_HWUI_CACHE_TEXTURE_H
18#define ANDROID_HWUI_CACHE_TEXTURE_H
19
20#include <GLES2/gl2.h>
21
22#include <SkScalerContext.h>
23
24#include <utils/Log.h>
25
26#include "FontUtil.h"
27#include "Rect.h"
28
29namespace android {
30namespace uirenderer {
31
32/**
33 * CacheBlock is a node in a linked list of current free space areas in a CacheTexture.
34 * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right.
35 * When we add a glyph to the cache, we see if it fits within one of the existing columns that
36 * have already been started (this is the case if the glyph fits vertically as well as
37 * horizontally, and if its width is sufficiently close to the column width to avoid
38 * sub-optimal packing of small glyphs into wide columns). If there is no column in which the
39 * glyph fits, we check the final node, which is the remaining space in the cache, creating
40 * a new column as appropriate.
41 *
42 * As columns fill up, we remove their CacheBlock from the list to avoid having to check
43 * small blocks in the future.
44 */
45struct CacheBlock {
46    uint16_t mX;
47    uint16_t mY;
48    uint16_t mWidth;
49    uint16_t mHeight;
50    CacheBlock* mNext;
51    CacheBlock* mPrev;
52
53    CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false):
54            mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) {
55    }
56
57    static CacheBlock* insertBlock(CacheBlock* head, CacheBlock* newBlock);
58
59    static CacheBlock* removeBlock(CacheBlock* head, CacheBlock* blockToRemove);
60
61    void output() {
62        CacheBlock* currBlock = this;
63        while (currBlock) {
64            ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d",
65                    currBlock, currBlock->mX, currBlock->mY, currBlock->mWidth, currBlock->mHeight);
66            currBlock = currBlock->mNext;
67        }
68    }
69};
70
71class CacheTexture {
72public:
73    CacheTexture(uint16_t width, uint16_t height) :
74            mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
75            mLinearFiltering(false), mDirty(false), mNumGlyphs(0) {
76        mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
77                mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
78    }
79
80    ~CacheTexture() {
81        releaseTexture();
82        reset();
83    }
84
85    void reset() {
86        // Delete existing cache blocks
87        while (mCacheBlocks != NULL) {
88            CacheBlock* tmpBlock = mCacheBlocks;
89            mCacheBlocks = mCacheBlocks->mNext;
90            delete tmpBlock;
91        }
92        mNumGlyphs = 0;
93    }
94
95    void init() {
96        // reset, then create a new remainder space to start again
97        reset();
98        mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
99                mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
100    }
101
102    void releaseTexture() {
103        if (mTexture) {
104            delete[] mTexture;
105            mTexture = NULL;
106        }
107        if (mTextureId) {
108            glDeleteTextures(1, &mTextureId);
109            mTextureId = 0;
110        }
111        mDirty = false;
112    }
113
114    /**
115     * This method assumes that the proper texture unit is active.
116     */
117    void allocateTexture() {
118        if (!mTexture) {
119            mTexture = new uint8_t[mWidth * mHeight];
120        }
121
122        if (!mTextureId) {
123            glGenTextures(1, &mTextureId);
124
125            glBindTexture(GL_TEXTURE_2D, mTextureId);
126            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
127            // Initialize texture dimensions
128            glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
129                    GL_ALPHA, GL_UNSIGNED_BYTE, 0);
130
131            const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
132            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
133            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
134
135            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
136            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
137        }
138    }
139
140    bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
141
142    inline uint16_t getWidth() const {
143        return mWidth;
144    }
145
146    inline uint16_t getHeight() const {
147        return mHeight;
148    }
149
150    inline const Rect* getDirtyRect() const {
151        return &mDirtyRect;
152    }
153
154    inline uint8_t* getTexture() const {
155        return mTexture;
156    }
157
158    GLuint getTextureId() {
159        allocateTexture();
160        return mTextureId;
161    }
162
163    inline bool isDirty() const {
164        return mDirty;
165    }
166
167    inline void setDirty(bool dirty) {
168        mDirty = dirty;
169        if (!dirty) {
170            mDirtyRect.setEmpty();
171        }
172    }
173
174    inline bool getLinearFiltering() const {
175        return mLinearFiltering;
176    }
177
178    /**
179     * This method assumes that the proper texture unit is active.
180     */
181    void setLinearFiltering(bool linearFiltering, bool bind = true) {
182        if (linearFiltering != mLinearFiltering) {
183            mLinearFiltering = linearFiltering;
184
185            const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
186            if (bind) glBindTexture(GL_TEXTURE_2D, getTextureId());
187            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
188            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
189        }
190    }
191
192    inline uint16_t getGlyphCount() const {
193        return mNumGlyphs;
194    }
195
196private:
197    uint8_t* mTexture;
198    GLuint mTextureId;
199    uint16_t mWidth;
200    uint16_t mHeight;
201    bool mLinearFiltering;
202    bool mDirty;
203    uint16_t mNumGlyphs;
204    CacheBlock* mCacheBlocks;
205    Rect mDirtyRect;
206};
207
208}; // namespace uirenderer
209}; // namespace android
210
211#endif // ANDROID_HWUI_CACHE_TEXTURE_H
212