• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 #include <SkBitmap.h>
18 #include <SkCanvas.h>
19 #include <SkColor.h>
20 #include <SkColorFilter.h>
21 #include <SkMaskFilter.h>
22 #include <SkPaint.h>
23 #include <SkPath.h>
24 #include <SkPathEffect.h>
25 #include <SkRect.h>
26 
27 #include <utils/JenkinsHash.h>
28 #include <utils/Trace.h>
29 
30 #include "Caches.h"
31 #include "PathCache.h"
32 
33 #include "thread/Signal.h"
34 #include "thread/TaskProcessor.h"
35 
36 #include <cutils/properties.h>
37 
38 namespace android {
39 namespace uirenderer {
40 
41 static constexpr size_t PATH_CACHE_COUNT_LIMIT = 256;
42 
43 template <class T>
compareWidthHeight(const T & lhs,const T & rhs)44 static bool compareWidthHeight(const T& lhs, const T& rhs) {
45     return (lhs.mWidth == rhs.mWidth) && (lhs.mHeight == rhs.mHeight);
46 }
47 
compareRoundRects(const PathDescription::Shape::RoundRect & lhs,const PathDescription::Shape::RoundRect & rhs)48 static bool compareRoundRects(const PathDescription::Shape::RoundRect& lhs,
49                               const PathDescription::Shape::RoundRect& rhs) {
50     return compareWidthHeight(lhs, rhs) && lhs.mRx == rhs.mRx && lhs.mRy == rhs.mRy;
51 }
52 
compareArcs(const PathDescription::Shape::Arc & lhs,const PathDescription::Shape::Arc & rhs)53 static bool compareArcs(const PathDescription::Shape::Arc& lhs,
54                         const PathDescription::Shape::Arc& rhs) {
55     return compareWidthHeight(lhs, rhs) && lhs.mStartAngle == rhs.mStartAngle &&
56            lhs.mSweepAngle == rhs.mSweepAngle && lhs.mUseCenter == rhs.mUseCenter;
57 }
58 
59 ///////////////////////////////////////////////////////////////////////////////
60 // Cache entries
61 ///////////////////////////////////////////////////////////////////////////////
62 
PathDescription()63 PathDescription::PathDescription()
64         : type(ShapeType::None)
65         , join(SkPaint::kDefault_Join)
66         , cap(SkPaint::kDefault_Cap)
67         , style(SkPaint::kFill_Style)
68         , miter(4.0f)
69         , strokeWidth(1.0f)
70         , pathEffect(nullptr) {
71     // Shape bits should be set to zeroes, because they are used for hash calculation.
72     memset(&shape, 0, sizeof(Shape));
73 }
74 
PathDescription(ShapeType type,const SkPaint * paint)75 PathDescription::PathDescription(ShapeType type, const SkPaint* paint)
76         : type(type)
77         , join(paint->getStrokeJoin())
78         , cap(paint->getStrokeCap())
79         , style(paint->getStyle())
80         , miter(paint->getStrokeMiter())
81         , strokeWidth(paint->getStrokeWidth())
82         , pathEffect(paint->getPathEffect()) {
83     // Shape bits should be set to zeroes, because they are used for hash calculation.
84     memset(&shape, 0, sizeof(Shape));
85 }
86 
hash() const87 hash_t PathDescription::hash() const {
88     uint32_t hash = JenkinsHashMix(0, static_cast<int>(type));
89     hash = JenkinsHashMix(hash, join);
90     hash = JenkinsHashMix(hash, cap);
91     hash = JenkinsHashMix(hash, style);
92     hash = JenkinsHashMix(hash, android::hash_type(miter));
93     hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
94     hash = JenkinsHashMix(hash, android::hash_type(pathEffect));
95     hash = JenkinsHashMixBytes(hash, (uint8_t*)&shape, sizeof(Shape));
96     return JenkinsHashWhiten(hash);
97 }
98 
operator ==(const PathDescription & rhs) const99 bool PathDescription::operator==(const PathDescription& rhs) const {
100     if (type != rhs.type) return false;
101     if (join != rhs.join) return false;
102     if (cap != rhs.cap) return false;
103     if (style != rhs.style) return false;
104     if (miter != rhs.miter) return false;
105     if (strokeWidth != rhs.strokeWidth) return false;
106     if (pathEffect != rhs.pathEffect) return false;
107     switch (type) {
108         case ShapeType::None:
109             return 0;
110         case ShapeType::Rect:
111             return compareWidthHeight(shape.rect, rhs.shape.rect);
112         case ShapeType::RoundRect:
113             return compareRoundRects(shape.roundRect, rhs.shape.roundRect);
114         case ShapeType::Circle:
115             return shape.circle.mRadius == rhs.shape.circle.mRadius;
116         case ShapeType::Oval:
117             return compareWidthHeight(shape.oval, rhs.shape.oval);
118         case ShapeType::Arc:
119             return compareArcs(shape.arc, rhs.shape.arc);
120         case ShapeType::Path:
121             return shape.path.mGenerationID == rhs.shape.path.mGenerationID;
122     }
123 }
124 
125 ///////////////////////////////////////////////////////////////////////////////
126 // Utilities
127 ///////////////////////////////////////////////////////////////////////////////
128 
computePathBounds(const SkPath * path,const SkPaint * paint,PathTexture * texture,uint32_t & width,uint32_t & height)129 static void computePathBounds(const SkPath* path, const SkPaint* paint, PathTexture* texture,
130                               uint32_t& width, uint32_t& height) {
131     const SkRect& bounds = path->getBounds();
132     const float pathWidth = std::max(bounds.width(), 1.0f);
133     const float pathHeight = std::max(bounds.height(), 1.0f);
134 
135     texture->left = floorf(bounds.fLeft);
136     texture->top = floorf(bounds.fTop);
137 
138     texture->offset = (int)floorf(std::max(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f);
139 
140     width = uint32_t(pathWidth + texture->offset * 2.0 + 0.5);
141     height = uint32_t(pathHeight + texture->offset * 2.0 + 0.5);
142 }
143 
initPaint(SkPaint & paint)144 static void initPaint(SkPaint& paint) {
145     // Make sure the paint is opaque, color, alpha, filter, etc.
146     // will be applied later when compositing the alpha8 texture
147     paint.setColor(SK_ColorBLACK);
148     paint.setAlpha(255);
149     paint.setColorFilter(nullptr);
150     paint.setMaskFilter(nullptr);
151     paint.setShader(nullptr);
152     paint.setBlendMode(SkBlendMode::kSrc);
153 }
154 
drawPath(const SkPath * path,const SkPaint * paint,PathTexture * texture,uint32_t maxTextureSize)155 static sk_sp<Bitmap> drawPath(const SkPath* path, const SkPaint* paint, PathTexture* texture,
156                               uint32_t maxTextureSize) {
157     uint32_t width, height;
158     computePathBounds(path, paint, texture, width, height);
159     if (width > maxTextureSize || height > maxTextureSize) {
160         ALOGW("Shape too large to be rendered into a texture (%dx%d, max=%dx%d)", width, height,
161               maxTextureSize, maxTextureSize);
162         return nullptr;
163     }
164 
165     sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height));
166     SkPaint pathPaint(*paint);
167     initPaint(pathPaint);
168 
169     SkBitmap skBitmap;
170     bitmap->getSkBitmap(&skBitmap);
171     skBitmap.eraseColor(0);
172     SkCanvas canvas(skBitmap);
173     canvas.translate(-texture->left + texture->offset, -texture->top + texture->offset);
174     canvas.drawPath(*path, pathPaint);
175     return bitmap;
176 }
177 
178 ///////////////////////////////////////////////////////////////////////////////
179 // Cache constructor/destructor
180 ///////////////////////////////////////////////////////////////////////////////
181 
PathCache()182 PathCache::PathCache()
183         : mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity)
184         , mSize(0)
185         , mMaxSize(DeviceInfo::multiplyByResolution(4)) {
186     mCache.setOnEntryRemovedListener(this);
187     mMaxTextureSize = DeviceInfo::get()->maxTextureSize();
188     mDebugEnabled = Properties::debugLevel & kDebugCaches;
189 }
190 
~PathCache()191 PathCache::~PathCache() {
192     mCache.clear();
193 }
194 
195 ///////////////////////////////////////////////////////////////////////////////
196 // Size management
197 ///////////////////////////////////////////////////////////////////////////////
198 
getSize()199 uint32_t PathCache::getSize() {
200     return mSize;
201 }
202 
getMaxSize()203 uint32_t PathCache::getMaxSize() {
204     return mMaxSize;
205 }
206 
207 ///////////////////////////////////////////////////////////////////////////////
208 // Callbacks
209 ///////////////////////////////////////////////////////////////////////////////
210 
operator ()(PathDescription & entry,PathTexture * & texture)211 void PathCache::operator()(PathDescription& entry, PathTexture*& texture) {
212     removeTexture(texture);
213 }
214 
215 ///////////////////////////////////////////////////////////////////////////////
216 // Caching
217 ///////////////////////////////////////////////////////////////////////////////
218 
removeTexture(PathTexture * texture)219 void PathCache::removeTexture(PathTexture* texture) {
220     if (texture) {
221         const uint32_t size = texture->width() * texture->height();
222 
223         // If there is a pending task we must wait for it to return
224         // before attempting our cleanup
225         const sp<PathTask>& task = texture->task();
226         if (task != nullptr) {
227             task->getResult();
228             texture->clearTask();
229         } else {
230             // If there is a pending task, the path was not added
231             // to the cache and the size wasn't increased
232             if (size > mSize) {
233                 ALOGE("Removing path texture of size %d will leave "
234                       "the cache in an inconsistent state",
235                       size);
236             }
237             mSize -= size;
238         }
239 
240         PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d", texture->id, size, mSize);
241         if (mDebugEnabled) {
242             ALOGD("Shape deleted, size = %d", size);
243         }
244 
245         texture->deleteTexture();
246         delete texture;
247     }
248 }
249 
purgeCache(uint32_t width,uint32_t height)250 void PathCache::purgeCache(uint32_t width, uint32_t height) {
251     const uint32_t size = width * height;
252     // Don't even try to cache a bitmap that's bigger than the cache
253     if (size < mMaxSize) {
254         while (mSize + size > mMaxSize) {
255             mCache.removeOldest();
256         }
257     }
258 }
259 
trim()260 void PathCache::trim() {
261     while (mSize > mMaxSize || mCache.size() > PATH_CACHE_COUNT_LIMIT) {
262         LOG_ALWAYS_FATAL_IF(!mCache.size(),
263                             "Inconsistent mSize! Ran out of items to remove!"
264                             " mSize = %u, mMaxSize = %u",
265                             mSize, mMaxSize);
266         mCache.removeOldest();
267     }
268 }
269 
addTexture(const PathDescription & entry,const SkPath * path,const SkPaint * paint)270 PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath* path,
271                                    const SkPaint* paint) {
272     ATRACE_NAME("Generate Path Texture");
273 
274     PathTexture* texture = new PathTexture(Caches::getInstance(), path->getGenerationID());
275     sk_sp<Bitmap> bitmap(drawPath(path, paint, texture, mMaxTextureSize));
276     if (!bitmap) {
277         delete texture;
278         return nullptr;
279     }
280 
281     purgeCache(bitmap->width(), bitmap->height());
282     generateTexture(entry, *bitmap, texture);
283     return texture;
284 }
285 
generateTexture(const PathDescription & entry,Bitmap & bitmap,PathTexture * texture,bool addToCache)286 void PathCache::generateTexture(const PathDescription& entry, Bitmap& bitmap, PathTexture* texture,
287                                 bool addToCache) {
288     generateTexture(bitmap, texture);
289 
290     // Note here that we upload to a texture even if it's bigger than mMaxSize.
291     // Such an entry in mCache will only be temporary, since it will be evicted
292     // immediately on trim, or on any other Path entering the cache.
293     uint32_t size = texture->width() * texture->height();
294     mSize += size;
295     PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d", texture->id, size, mSize);
296     if (mDebugEnabled) {
297         ALOGD("Shape created, size = %d", size);
298     }
299     if (addToCache) {
300         mCache.put(entry, texture);
301     }
302 }
303 
clear()304 void PathCache::clear() {
305     mCache.clear();
306 }
307 
generateTexture(Bitmap & bitmap,Texture * texture)308 void PathCache::generateTexture(Bitmap& bitmap, Texture* texture) {
309     ATRACE_NAME("Upload Path Texture");
310     texture->upload(bitmap);
311     texture->setFilter(GL_LINEAR);
312 }
313 
314 ///////////////////////////////////////////////////////////////////////////////
315 // Path precaching
316 ///////////////////////////////////////////////////////////////////////////////
317 
PathProcessor(Caches & caches)318 PathCache::PathProcessor::PathProcessor(Caches& caches)
319         : TaskProcessor<sk_sp<Bitmap> >(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) {}
320 
onProcess(const sp<Task<sk_sp<Bitmap>>> & task)321 void PathCache::PathProcessor::onProcess(const sp<Task<sk_sp<Bitmap> > >& task) {
322     PathTask* t = static_cast<PathTask*>(task.get());
323     ATRACE_NAME("pathPrecache");
324 
325     t->setResult(drawPath(&t->path, &t->paint, t->texture, mMaxTextureSize));
326 }
327 
328 ///////////////////////////////////////////////////////////////////////////////
329 // Paths
330 ///////////////////////////////////////////////////////////////////////////////
331 
removeDeferred(const SkPath * path)332 void PathCache::removeDeferred(const SkPath* path) {
333     Mutex::Autolock l(mLock);
334     mGarbage.push_back(path->getGenerationID());
335 }
336 
clearGarbage()337 void PathCache::clearGarbage() {
338     Vector<PathDescription> pathsToRemove;
339 
340     {  // scope for the mutex
341         Mutex::Autolock l(mLock);
342         for (const uint32_t generationID : mGarbage) {
343             LruCache<PathDescription, PathTexture*>::Iterator iter(mCache);
344             while (iter.next()) {
345                 const PathDescription& key = iter.key();
346                 if (key.type == ShapeType::Path && key.shape.path.mGenerationID == generationID) {
347                     pathsToRemove.push(key);
348                 }
349             }
350         }
351         mGarbage.clear();
352     }
353 
354     for (size_t i = 0; i < pathsToRemove.size(); i++) {
355         mCache.remove(pathsToRemove.itemAt(i));
356     }
357 }
358 
get(const SkPath * path,const SkPaint * paint)359 PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) {
360     PathDescription entry(ShapeType::Path, paint);
361     entry.shape.path.mGenerationID = path->getGenerationID();
362 
363     PathTexture* texture = mCache.get(entry);
364 
365     if (!texture) {
366         texture = addTexture(entry, path, paint);
367     } else {
368         // A bitmap is attached to the texture, this means we need to
369         // upload it as a GL texture
370         const sp<PathTask>& task = texture->task();
371         if (task != nullptr) {
372             // But we must first wait for the worker thread to be done
373             // producing the bitmap, so let's wait
374             sk_sp<Bitmap> bitmap = task->getResult();
375             if (bitmap) {
376                 generateTexture(entry, *bitmap, texture, false);
377                 texture->clearTask();
378             } else {
379                 texture->clearTask();
380                 texture = nullptr;
381                 mCache.remove(entry);
382             }
383         }
384     }
385 
386     return texture;
387 }
388 
remove(const SkPath * path,const SkPaint * paint)389 void PathCache::remove(const SkPath* path, const SkPaint* paint) {
390     PathDescription entry(ShapeType::Path, paint);
391     entry.shape.path.mGenerationID = path->getGenerationID();
392     mCache.remove(entry);
393 }
394 
precache(const SkPath * path,const SkPaint * paint)395 void PathCache::precache(const SkPath* path, const SkPaint* paint) {
396     if (!Caches::getInstance().tasks.canRunTasks()) {
397         return;
398     }
399 
400     PathDescription entry(ShapeType::Path, paint);
401     entry.shape.path.mGenerationID = path->getGenerationID();
402 
403     PathTexture* texture = mCache.get(entry);
404 
405     bool generate = false;
406     if (!texture) {
407         generate = true;
408     }
409 
410     if (generate) {
411         // It is important to specify the generation ID so we do not
412         // attempt to precache the same path several times
413         texture = new PathTexture(Caches::getInstance(), path->getGenerationID());
414         sp<PathTask> task = new PathTask(path, paint, texture);
415         texture->setTask(task);
416 
417         // During the precaching phase we insert path texture objects into
418         // the cache that do not point to any GL texture. They are instead
419         // treated as a task for the precaching worker thread. This is why
420         // we do not check the cache limit when inserting these objects.
421         // The conversion into GL texture will happen in get(), when a client
422         // asks for a path texture. This is also when the cache limit will
423         // be enforced.
424         mCache.put(entry, texture);
425 
426         if (mProcessor == nullptr) {
427             mProcessor = new PathProcessor(Caches::getInstance());
428         }
429         mProcessor->add(task);
430     }
431 }
432 
433 ///////////////////////////////////////////////////////////////////////////////
434 // Rounded rects
435 ///////////////////////////////////////////////////////////////////////////////
436 
getRoundRect(float width,float height,float rx,float ry,const SkPaint * paint)437 PathTexture* PathCache::getRoundRect(float width, float height, float rx, float ry,
438                                      const SkPaint* paint) {
439     PathDescription entry(ShapeType::RoundRect, paint);
440     entry.shape.roundRect.mWidth = width;
441     entry.shape.roundRect.mHeight = height;
442     entry.shape.roundRect.mRx = rx;
443     entry.shape.roundRect.mRy = ry;
444 
445     PathTexture* texture = get(entry);
446 
447     if (!texture) {
448         SkPath path;
449         SkRect r;
450         r.set(0.0f, 0.0f, width, height);
451         path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
452 
453         texture = addTexture(entry, &path, paint);
454     }
455 
456     return texture;
457 }
458 
459 ///////////////////////////////////////////////////////////////////////////////
460 // Circles
461 ///////////////////////////////////////////////////////////////////////////////
462 
getCircle(float radius,const SkPaint * paint)463 PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) {
464     PathDescription entry(ShapeType::Circle, paint);
465     entry.shape.circle.mRadius = radius;
466 
467     PathTexture* texture = get(entry);
468 
469     if (!texture) {
470         SkPath path;
471         path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
472 
473         texture = addTexture(entry, &path, paint);
474     }
475 
476     return texture;
477 }
478 
479 ///////////////////////////////////////////////////////////////////////////////
480 // Ovals
481 ///////////////////////////////////////////////////////////////////////////////
482 
getOval(float width,float height,const SkPaint * paint)483 PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) {
484     PathDescription entry(ShapeType::Oval, paint);
485     entry.shape.oval.mWidth = width;
486     entry.shape.oval.mHeight = height;
487 
488     PathTexture* texture = get(entry);
489 
490     if (!texture) {
491         SkPath path;
492         SkRect r;
493         r.set(0.0f, 0.0f, width, height);
494         path.addOval(r, SkPath::kCW_Direction);
495 
496         texture = addTexture(entry, &path, paint);
497     }
498 
499     return texture;
500 }
501 
502 ///////////////////////////////////////////////////////////////////////////////
503 // Rects
504 ///////////////////////////////////////////////////////////////////////////////
505 
getRect(float width,float height,const SkPaint * paint)506 PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) {
507     PathDescription entry(ShapeType::Rect, paint);
508     entry.shape.rect.mWidth = width;
509     entry.shape.rect.mHeight = height;
510 
511     PathTexture* texture = get(entry);
512 
513     if (!texture) {
514         SkPath path;
515         SkRect r;
516         r.set(0.0f, 0.0f, width, height);
517         path.addRect(r, SkPath::kCW_Direction);
518 
519         texture = addTexture(entry, &path, paint);
520     }
521 
522     return texture;
523 }
524 
525 ///////////////////////////////////////////////////////////////////////////////
526 // Arcs
527 ///////////////////////////////////////////////////////////////////////////////
528 
getArc(float width,float height,float startAngle,float sweepAngle,bool useCenter,const SkPaint * paint)529 PathTexture* PathCache::getArc(float width, float height, float startAngle, float sweepAngle,
530                                bool useCenter, const SkPaint* paint) {
531     PathDescription entry(ShapeType::Arc, paint);
532     entry.shape.arc.mWidth = width;
533     entry.shape.arc.mHeight = height;
534     entry.shape.arc.mStartAngle = startAngle;
535     entry.shape.arc.mSweepAngle = sweepAngle;
536     entry.shape.arc.mUseCenter = useCenter;
537 
538     PathTexture* texture = get(entry);
539 
540     if (!texture) {
541         SkPath path;
542         SkRect r;
543         r.set(0.0f, 0.0f, width, height);
544         if (useCenter) {
545             path.moveTo(r.centerX(), r.centerY());
546         }
547         path.arcTo(r, startAngle, sweepAngle, !useCenter);
548         if (useCenter) {
549             path.close();
550         }
551 
552         texture = addTexture(entry, &path, paint);
553     }
554 
555     return texture;
556 }
557 
558 };  // namespace uirenderer
559 };  // namespace android
560