blob: f180e942adc6b469fb6315d97a3efdd356ad180f [file] [log] [blame]
Romain Guy01d58e42011-01-19 21:54:02 -08001/*
2 * Copyright (C) 2011 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_SHAPE_CACHE_H
18#define ANDROID_HWUI_SHAPE_CACHE_H
19
20#include <GLES2/gl2.h>
21
Romain Guyff26a0c2011-01-20 11:35:46 -080022#include <SkBitmap.h>
Romain Guy01d58e42011-01-19 21:54:02 -080023#include <SkCanvas.h>
Romain Guyff26a0c2011-01-20 11:35:46 -080024#include <SkPaint.h>
25#include <SkPath.h>
Romain Guy01d58e42011-01-19 21:54:02 -080026#include <SkRect.h>
27
Romain Guyff26a0c2011-01-20 11:35:46 -080028#include "Debug.h"
Romain Guy01d58e42011-01-19 21:54:02 -080029#include "Properties.h"
Romain Guyff26a0c2011-01-20 11:35:46 -080030#include "Texture.h"
31#include "utils/Compare.h"
32#include "utils/GenerationCache.h"
Romain Guy01d58e42011-01-19 21:54:02 -080033
34namespace android {
35namespace uirenderer {
36
37///////////////////////////////////////////////////////////////////////////////
38// Defines
39///////////////////////////////////////////////////////////////////////////////
40
41// Debug
42#if DEBUG_SHAPES
Steve Block5baa3a62011-12-20 16:23:08 +000043 #define SHAPE_LOGD(...) ALOGD(__VA_ARGS__)
Romain Guy01d58e42011-01-19 21:54:02 -080044#else
45 #define SHAPE_LOGD(...)
46#endif
47
48///////////////////////////////////////////////////////////////////////////////
49// Classes
50///////////////////////////////////////////////////////////////////////////////
51
52/**
Romain Guyff26a0c2011-01-20 11:35:46 -080053 * Alpha texture used to represent a path.
54 */
55struct PathTexture: public Texture {
56 PathTexture(): Texture() {
57 }
58
59 /**
60 * Left coordinate of the path bounds.
61 */
62 float left;
63 /**
64 * Top coordinate of the path bounds.
65 */
66 float top;
67 /**
68 * Offset to draw the path at the correct origin.
69 */
70 float offset;
71}; // struct PathTexture
72
73/**
Romain Guy01d58e42011-01-19 21:54:02 -080074 * Describe a shape in the shape cache.
75 */
76struct ShapeCacheEntry {
77 enum ShapeType {
78 kShapeNone,
Romain Guyc1cd9ba32011-01-23 14:18:41 -080079 kShapeRect,
Romain Guy01d58e42011-01-19 21:54:02 -080080 kShapeRoundRect,
81 kShapeCircle,
82 kShapeOval,
Romain Guyff26a0c2011-01-20 11:35:46 -080083 kShapeArc,
84 kShapePath
Romain Guy01d58e42011-01-19 21:54:02 -080085 };
86
87 ShapeCacheEntry() {
88 shapeType = kShapeNone;
89 join = SkPaint::kDefault_Join;
90 cap = SkPaint::kDefault_Cap;
91 style = SkPaint::kFill_Style;
Romain Guy1af23a32011-03-24 16:03:55 -070092 float v = 4.0f;
93 miter = *(uint32_t*) &v;
94 v = 1.0f;
95 strokeWidth = *(uint32_t*) &v;
96 pathEffect = NULL;
Romain Guy01d58e42011-01-19 21:54:02 -080097 }
98
Romain Guy01d58e42011-01-19 21:54:02 -080099 ShapeCacheEntry(ShapeType type, SkPaint* paint) {
100 shapeType = type;
101 join = paint->getStrokeJoin();
102 cap = paint->getStrokeCap();
103 float v = paint->getStrokeMiter();
104 miter = *(uint32_t*) &v;
105 v = paint->getStrokeWidth();
106 strokeWidth = *(uint32_t*) &v;
107 style = paint->getStyle();
Romain Guyb29cfbf2011-03-18 16:24:19 -0700108 pathEffect = paint->getPathEffect();
Romain Guy01d58e42011-01-19 21:54:02 -0800109 }
110
111 virtual ~ShapeCacheEntry() {
112 }
113
Romain Guy01d58e42011-01-19 21:54:02 -0800114 ShapeType shapeType;
115 SkPaint::Join join;
116 SkPaint::Cap cap;
117 SkPaint::Style style;
118 uint32_t miter;
119 uint32_t strokeWidth;
Romain Guyb29cfbf2011-03-18 16:24:19 -0700120 SkPathEffect* pathEffect;
Romain Guy01d58e42011-01-19 21:54:02 -0800121
122 bool operator<(const ShapeCacheEntry& rhs) const {
123 LTE_INT(shapeType) {
124 LTE_INT(join) {
125 LTE_INT(cap) {
126 LTE_INT(style) {
127 LTE_INT(miter) {
128 LTE_INT(strokeWidth) {
Romain Guyb29cfbf2011-03-18 16:24:19 -0700129 LTE_INT(pathEffect) {
130 return lessThan(rhs);
131 }
Romain Guy01d58e42011-01-19 21:54:02 -0800132 }
133 }
134 }
135 }
136 }
137 }
138 return false;
139 }
140
141protected:
142 virtual bool lessThan(const ShapeCacheEntry& rhs) const {
143 return false;
144 }
145}; // struct ShapeCacheEntry
146
147
148struct RoundRectShapeCacheEntry: public ShapeCacheEntry {
149 RoundRectShapeCacheEntry(float width, float height, float rx, float ry, SkPaint* paint):
150 ShapeCacheEntry(ShapeCacheEntry::kShapeRoundRect, paint) {
151 mWidth = *(uint32_t*) &width;
152 mHeight = *(uint32_t*) &height;
153 mRx = *(uint32_t*) &rx;
154 mRy = *(uint32_t*) &ry;
155 }
156
157 RoundRectShapeCacheEntry(): ShapeCacheEntry() {
158 mWidth = 0;
159 mHeight = 0;
160 mRx = 0;
161 mRy = 0;
162 }
163
Romain Guy01d58e42011-01-19 21:54:02 -0800164 bool lessThan(const ShapeCacheEntry& r) const {
165 const RoundRectShapeCacheEntry& rhs = (const RoundRectShapeCacheEntry&) r;
166 LTE_INT(mWidth) {
167 LTE_INT(mHeight) {
168 LTE_INT(mRx) {
169 LTE_INT(mRy) {
170 return false;
171 }
172 }
173 }
174 }
175 return false;
176 }
177
178private:
179 uint32_t mWidth;
180 uint32_t mHeight;
181 uint32_t mRx;
182 uint32_t mRy;
183}; // RoundRectShapeCacheEntry
184
185struct CircleShapeCacheEntry: public ShapeCacheEntry {
186 CircleShapeCacheEntry(float radius, SkPaint* paint):
187 ShapeCacheEntry(ShapeCacheEntry::kShapeCircle, paint) {
188 mRadius = *(uint32_t*) &radius;
189 }
190
191 CircleShapeCacheEntry(): ShapeCacheEntry() {
192 mRadius = 0;
193 }
194
Romain Guy01d58e42011-01-19 21:54:02 -0800195 bool lessThan(const ShapeCacheEntry& r) const {
196 const CircleShapeCacheEntry& rhs = (const CircleShapeCacheEntry&) r;
197 LTE_INT(mRadius) {
198 return false;
199 }
200 return false;
201 }
202
203private:
204 uint32_t mRadius;
205}; // CircleShapeCacheEntry
206
Romain Guyc1cd9ba32011-01-23 14:18:41 -0800207struct OvalShapeCacheEntry: public ShapeCacheEntry {
208 OvalShapeCacheEntry(float width, float height, SkPaint* paint):
209 ShapeCacheEntry(ShapeCacheEntry::kShapeOval, paint) {
210 mWidth = *(uint32_t*) &width;
211 mHeight = *(uint32_t*) &height;
212 }
213
214 OvalShapeCacheEntry(): ShapeCacheEntry() {
215 mWidth = mHeight = 0;
216 }
217
Romain Guyc1cd9ba32011-01-23 14:18:41 -0800218 bool lessThan(const ShapeCacheEntry& r) const {
219 const OvalShapeCacheEntry& rhs = (const OvalShapeCacheEntry&) r;
220 LTE_INT(mWidth) {
221 LTE_INT(mHeight) {
222 return false;
223 }
224 }
225 return false;
226 }
227
228private:
229 uint32_t mWidth;
230 uint32_t mHeight;
231}; // OvalShapeCacheEntry
232
233struct RectShapeCacheEntry: public ShapeCacheEntry {
234 RectShapeCacheEntry(float width, float height, SkPaint* paint):
235 ShapeCacheEntry(ShapeCacheEntry::kShapeRect, paint) {
236 mWidth = *(uint32_t*) &width;
237 mHeight = *(uint32_t*) &height;
238 }
239
240 RectShapeCacheEntry(): ShapeCacheEntry() {
241 mWidth = mHeight = 0;
242 }
243
Romain Guyc1cd9ba32011-01-23 14:18:41 -0800244 bool lessThan(const ShapeCacheEntry& r) const {
245 const RectShapeCacheEntry& rhs = (const RectShapeCacheEntry&) r;
246 LTE_INT(mWidth) {
247 LTE_INT(mHeight) {
248 return false;
249 }
250 }
251 return false;
252 }
253
254private:
255 uint32_t mWidth;
256 uint32_t mHeight;
257}; // RectShapeCacheEntry
258
Romain Guy8b2f5262011-01-23 16:15:02 -0800259struct ArcShapeCacheEntry: public ShapeCacheEntry {
260 ArcShapeCacheEntry(float width, float height, float startAngle, float sweepAngle,
261 bool useCenter, SkPaint* paint):
262 ShapeCacheEntry(ShapeCacheEntry::kShapeArc, paint) {
263 mWidth = *(uint32_t*) &width;
264 mHeight = *(uint32_t*) &height;
265 mStartAngle = *(uint32_t*) &startAngle;
266 mSweepAngle = *(uint32_t*) &sweepAngle;
267 mUseCenter = useCenter ? 1 : 0;
268 }
269
270 ArcShapeCacheEntry(): ShapeCacheEntry() {
271 mWidth = 0;
272 mHeight = 0;
273 mStartAngle = 0;
274 mSweepAngle = 0;
275 mUseCenter = 0;
276 }
277
Romain Guy8b2f5262011-01-23 16:15:02 -0800278 bool lessThan(const ShapeCacheEntry& r) const {
279 const ArcShapeCacheEntry& rhs = (const ArcShapeCacheEntry&) r;
280 LTE_INT(mWidth) {
281 LTE_INT(mHeight) {
282 LTE_INT(mStartAngle) {
283 LTE_INT(mSweepAngle) {
284 LTE_INT(mUseCenter) {
285 return false;
286 }
287 }
288 }
289 }
290 }
291 return false;
292 }
293
294private:
295 uint32_t mWidth;
296 uint32_t mHeight;
297 uint32_t mStartAngle;
298 uint32_t mSweepAngle;
299 uint32_t mUseCenter;
300}; // ArcShapeCacheEntry
301
Romain Guy01d58e42011-01-19 21:54:02 -0800302/**
303 * A simple LRU shape cache. The cache has a maximum size expressed in bytes.
304 * Any texture added to the cache causing the cache to grow beyond the maximum
305 * allowed size will also cause the oldest texture to be kicked out.
306 */
307template<typename Entry>
308class ShapeCache: public OnEntryRemoved<Entry, PathTexture*> {
309public:
Romain Guyff26a0c2011-01-20 11:35:46 -0800310 ShapeCache(const char* name, const char* propertyName, float defaultSize);
Romain Guy01d58e42011-01-19 21:54:02 -0800311 ~ShapeCache();
312
313 /**
314 * Used as a callback when an entry is removed from the cache.
315 * Do not invoke directly.
316 */
317 void operator()(Entry& path, PathTexture*& texture);
318
319 /**
320 * Clears the cache. This causes all textures to be deleted.
321 */
322 void clear();
323
324 /**
325 * Sets the maximum size of the cache in bytes.
326 */
327 void setMaxSize(uint32_t maxSize);
328 /**
329 * Returns the maximum size of the cache in bytes.
330 */
331 uint32_t getMaxSize();
332 /**
333 * Returns the current size of the cache in bytes.
334 */
335 uint32_t getSize();
336
337protected:
338 PathTexture* addTexture(const Entry& entry, const SkPath *path, const SkPaint* paint);
339
340 PathTexture* get(Entry entry) {
341 return mCache.get(entry);
342 }
343
Romain Guy01d58e42011-01-19 21:54:02 -0800344 void removeTexture(PathTexture* texture);
345
Romain Guy01d58e42011-01-19 21:54:02 -0800346 GenerationCache<Entry, PathTexture*> mCache;
347 uint32_t mSize;
348 uint32_t mMaxSize;
349 GLuint mMaxTextureSize;
350
Romain Guyff26a0c2011-01-20 11:35:46 -0800351 char* mName;
Romain Guy01d58e42011-01-19 21:54:02 -0800352 bool mDebugEnabled;
Romain Guyff26a0c2011-01-20 11:35:46 -0800353
354private:
355 /**
356 * Generates the texture from a bitmap into the specified texture structure.
357 */
358 void generateTexture(SkBitmap& bitmap, Texture* texture);
359
360 void init();
Romain Guy01d58e42011-01-19 21:54:02 -0800361}; // class ShapeCache
362
363class RoundRectShapeCache: public ShapeCache<RoundRectShapeCacheEntry> {
364public:
365 RoundRectShapeCache();
366
367 PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
368}; // class RoundRectShapeCache
369
370class CircleShapeCache: public ShapeCache<CircleShapeCacheEntry> {
371public:
372 CircleShapeCache();
373
374 PathTexture* getCircle(float radius, SkPaint* paint);
Romain Guyc1cd9ba32011-01-23 14:18:41 -0800375}; // class CircleShapeCache
Romain Guy01d58e42011-01-19 21:54:02 -0800376
Romain Guyc1cd9ba32011-01-23 14:18:41 -0800377class OvalShapeCache: public ShapeCache<OvalShapeCacheEntry> {
378public:
379 OvalShapeCache();
380
381 PathTexture* getOval(float width, float height, SkPaint* paint);
382}; // class OvalShapeCache
383
384class RectShapeCache: public ShapeCache<RectShapeCacheEntry> {
385public:
386 RectShapeCache();
387
388 PathTexture* getRect(float width, float height, SkPaint* paint);
389}; // class RectShapeCache
Romain Guy01d58e42011-01-19 21:54:02 -0800390
Romain Guy8b2f5262011-01-23 16:15:02 -0800391class ArcShapeCache: public ShapeCache<ArcShapeCacheEntry> {
392public:
393 ArcShapeCache();
394
395 PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
396 bool useCenter, SkPaint* paint);
397}; // class ArcShapeCache
398
Romain Guy01d58e42011-01-19 21:54:02 -0800399///////////////////////////////////////////////////////////////////////////////
400// Constructors/destructor
401///////////////////////////////////////////////////////////////////////////////
402
403template<class Entry>
Romain Guyff26a0c2011-01-20 11:35:46 -0800404ShapeCache<Entry>::ShapeCache(const char* name, const char* propertyName, float defaultSize):
Romain Guy01d58e42011-01-19 21:54:02 -0800405 mCache(GenerationCache<ShapeCacheEntry, PathTexture*>::kUnlimitedCapacity),
Romain Guyff26a0c2011-01-20 11:35:46 -0800406 mSize(0), mMaxSize(MB(defaultSize)) {
Romain Guy01d58e42011-01-19 21:54:02 -0800407 char property[PROPERTY_VALUE_MAX];
Romain Guyff26a0c2011-01-20 11:35:46 -0800408 if (property_get(propertyName, property, NULL) > 0) {
Romain Guyc9855a52011-01-21 21:14:15 -0800409 INIT_LOGD(" Setting %s cache size to %sMB", name, property);
Romain Guy01d58e42011-01-19 21:54:02 -0800410 setMaxSize(MB(atof(property)));
411 } else {
Romain Guyc9855a52011-01-21 21:14:15 -0800412 INIT_LOGD(" Using default %s cache size of %.2fMB", name, defaultSize);
Romain Guy01d58e42011-01-19 21:54:02 -0800413 }
Romain Guy01d58e42011-01-19 21:54:02 -0800414
Romain Guyff26a0c2011-01-20 11:35:46 -0800415 size_t len = strlen(name);
416 mName = new char[len + 1];
417 strcpy(mName, name);
418 mName[len] = '\0';
419
Romain Guy01d58e42011-01-19 21:54:02 -0800420 init();
421}
422
423template<class Entry>
424ShapeCache<Entry>::~ShapeCache() {
425 mCache.clear();
Romain Guyff26a0c2011-01-20 11:35:46 -0800426 delete[] mName;
Romain Guy01d58e42011-01-19 21:54:02 -0800427}
428
429template<class Entry>
430void ShapeCache<Entry>::init() {
431 mCache.setOnEntryRemovedListener(this);
432
433 GLint maxTextureSize;
434 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
435 mMaxTextureSize = maxTextureSize;
436
437 mDebugEnabled = readDebugLevel() & kDebugCaches;
438}
439
440///////////////////////////////////////////////////////////////////////////////
441// Size management
442///////////////////////////////////////////////////////////////////////////////
443
444template<class Entry>
445uint32_t ShapeCache<Entry>::getSize() {
446 return mSize;
447}
448
449template<class Entry>
450uint32_t ShapeCache<Entry>::getMaxSize() {
451 return mMaxSize;
452}
453
454template<class Entry>
455void ShapeCache<Entry>::setMaxSize(uint32_t maxSize) {
456 mMaxSize = maxSize;
457 while (mSize > mMaxSize) {
458 mCache.removeOldest();
459 }
460}
461
462///////////////////////////////////////////////////////////////////////////////
463// Callbacks
464///////////////////////////////////////////////////////////////////////////////
465
466template<class Entry>
467void ShapeCache<Entry>::operator()(Entry& path, PathTexture*& texture) {
468 removeTexture(texture);
469}
470
471///////////////////////////////////////////////////////////////////////////////
472// Caching
473///////////////////////////////////////////////////////////////////////////////
474
475template<class Entry>
476void ShapeCache<Entry>::removeTexture(PathTexture* texture) {
477 if (texture) {
478 const uint32_t size = texture->width * texture->height;
479 mSize -= size;
480
Romain Guyff26a0c2011-01-20 11:35:46 -0800481 SHAPE_LOGD("ShapeCache::callback: delete %s: name, size, mSize = %d, %d, %d",
482 mName, texture->id, size, mSize);
Romain Guy01d58e42011-01-19 21:54:02 -0800483 if (mDebugEnabled) {
Steve Block5baa3a62011-12-20 16:23:08 +0000484 ALOGD("Shape %s deleted, size = %d", mName, size);
Romain Guy01d58e42011-01-19 21:54:02 -0800485 }
486
487 glDeleteTextures(1, &texture->id);
488 delete texture;
489 }
490}
491
Romain Guy33f6beb2012-02-16 19:24:51 -0800492void computePathBounds(const SkPath *path, const SkPaint* paint,
493 float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
494
Romain Guy01d58e42011-01-19 21:54:02 -0800495template<class Entry>
496PathTexture* ShapeCache<Entry>::addTexture(const Entry& entry, const SkPath *path,
497 const SkPaint* paint) {
Romain Guy01d58e42011-01-19 21:54:02 -0800498
Romain Guy33f6beb2012-02-16 19:24:51 -0800499 float left, top, offset;
500 uint32_t width, height;
501 computePathBounds(path, paint, left, top, offset, width, height);
Romain Guy98029c82011-06-17 15:47:07 -0700502
503 if (width > mMaxTextureSize || height > mMaxTextureSize) {
Steve Block8564c8d2012-01-05 23:22:43 +0000504 ALOGW("Shape %s too large to be rendered into a texture (%dx%d, max=%dx%d)",
Romain Guy8f9a9f62011-12-05 11:53:26 -0800505 mName, width, height, mMaxTextureSize, mMaxTextureSize);
Romain Guy01d58e42011-01-19 21:54:02 -0800506 return NULL;
507 }
508
Romain Guy01d58e42011-01-19 21:54:02 -0800509 const uint32_t size = width * height;
510 // Don't even try to cache a bitmap that's bigger than the cache
511 if (size < mMaxSize) {
512 while (mSize + size > mMaxSize) {
513 mCache.removeOldest();
514 }
515 }
516
517 PathTexture* texture = new PathTexture;
Romain Guy33f6beb2012-02-16 19:24:51 -0800518 texture->left = left;
519 texture->top = top;
Romain Guy01d58e42011-01-19 21:54:02 -0800520 texture->offset = offset;
521 texture->width = width;
522 texture->height = height;
523 texture->generation = path->getGenerationID();
524
525 SkBitmap bitmap;
526 bitmap.setConfig(SkBitmap::kA8_Config, width, height);
527 bitmap.allocPixels();
528 bitmap.eraseColor(0);
529
530 SkPaint pathPaint(*paint);
531
532 // Make sure the paint is opaque, color, alpha, filter, etc.
533 // will be applied later when compositing the alpha8 texture
534 pathPaint.setColor(0xff000000);
535 pathPaint.setAlpha(255);
536 pathPaint.setColorFilter(NULL);
537 pathPaint.setMaskFilter(NULL);
538 pathPaint.setShader(NULL);
539 SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
Derek Sollenberger6062c592011-02-22 13:55:04 -0500540 SkSafeUnref(pathPaint.setXfermode(mode));
Romain Guy01d58e42011-01-19 21:54:02 -0800541
542 SkCanvas canvas(bitmap);
Romain Guy33f6beb2012-02-16 19:24:51 -0800543 canvas.translate(-left + offset, -top + offset);
Romain Guy01d58e42011-01-19 21:54:02 -0800544 canvas.drawPath(*path, pathPaint);
545
546 generateTexture(bitmap, texture);
547
548 if (size < mMaxSize) {
549 mSize += size;
Romain Guyff26a0c2011-01-20 11:35:46 -0800550 SHAPE_LOGD("ShapeCache::get: create %s: name, size, mSize = %d, %d, %d",
551 mName, texture->id, size, mSize);
Romain Guy01d58e42011-01-19 21:54:02 -0800552 if (mDebugEnabled) {
Steve Block5baa3a62011-12-20 16:23:08 +0000553 ALOGD("Shape %s created, size = %d", mName, size);
Romain Guy01d58e42011-01-19 21:54:02 -0800554 }
555 mCache.put(entry, texture);
556 } else {
557 texture->cleanup = true;
558 }
559
560 return texture;
561}
562
563template<class Entry>
564void ShapeCache<Entry>::clear() {
565 mCache.clear();
566}
567
568template<class Entry>
569void ShapeCache<Entry>::generateTexture(SkBitmap& bitmap, Texture* texture) {
570 SkAutoLockPixels alp(bitmap);
571 if (!bitmap.readyToDraw()) {
Steve Block3762c312012-01-06 19:20:56 +0000572 ALOGE("Cannot generate texture from bitmap");
Romain Guy01d58e42011-01-19 21:54:02 -0800573 return;
574 }
575
576 glGenTextures(1, &texture->id);
577
578 glBindTexture(GL_TEXTURE_2D, texture->id);
579 // Textures are Alpha8
580 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
581
582 texture->blend = true;
583 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
584 GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
585
Romain Guyd21b6e12011-11-30 20:21:23 -0800586 texture->setFilter(GL_LINEAR);
587 texture->setWrap(GL_CLAMP_TO_EDGE);
Romain Guy01d58e42011-01-19 21:54:02 -0800588}
589
590}; // namespace uirenderer
591}; // namespace android
592
593#endif // ANDROID_HWUI_SHAPE_CACHE_H