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