blob: 461e8190c9746cc3673aa3faf48514dc24383a96 [file] [log] [blame]
Chris Craik05f3d6e2014-06-02 16:27:04 -07001/*
2 * Copyright (C) 2014 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
Chris Craik05f3d6e2014-06-02 16:27:04 -070017#include <utils/JenkinsHash.h>
18#include <utils/Trace.h>
19
20#include "Caches.h"
21#include "OpenGLRenderer.h"
22#include "PathTessellator.h"
23#include "ShadowTessellator.h"
24#include "TessellationCache.h"
25
26#include "thread/Signal.h"
27#include "thread/Task.h"
28#include "thread/TaskProcessor.h"
29
30namespace android {
31namespace uirenderer {
32
33///////////////////////////////////////////////////////////////////////////////
34// Cache entries
35///////////////////////////////////////////////////////////////////////////////
36
37TessellationCache::Description::Description()
38 : type(kNone)
Chris Craik6ac174b2014-06-17 13:47:05 -070039 , scaleX(1.0f)
40 , scaleY(1.0f)
Chris Craiked4ef0b2014-06-12 13:27:30 -070041 , aa(false)
Chris Craik05f3d6e2014-06-02 16:27:04 -070042 , cap(SkPaint::kDefault_Cap)
43 , style(SkPaint::kFill_Style)
44 , strokeWidth(1.0f) {
45 memset(&shape, 0, sizeof(Shape));
46}
47
Chris Craik6ac174b2014-06-17 13:47:05 -070048TessellationCache::Description::Description(Type type, const Matrix4& transform, const SkPaint& paint)
Chris Craik05f3d6e2014-06-02 16:27:04 -070049 : type(type)
Chris Craik6ac174b2014-06-17 13:47:05 -070050 , aa(paint.isAntiAlias())
51 , cap(paint.getStrokeCap())
52 , style(paint.getStyle())
53 , strokeWidth(paint.getStrokeWidth()) {
54 PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
Chris Craik05f3d6e2014-06-02 16:27:04 -070055 memset(&shape, 0, sizeof(Shape));
56}
57
58hash_t TessellationCache::Description::hash() const {
59 uint32_t hash = JenkinsHashMix(0, type);
Chris Craiked4ef0b2014-06-12 13:27:30 -070060 hash = JenkinsHashMix(hash, aa);
Chris Craik05f3d6e2014-06-02 16:27:04 -070061 hash = JenkinsHashMix(hash, cap);
62 hash = JenkinsHashMix(hash, style);
63 hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
Chris Craik6ac174b2014-06-17 13:47:05 -070064 hash = JenkinsHashMix(hash, android::hash_type(scaleX));
65 hash = JenkinsHashMix(hash, android::hash_type(scaleY));
Chris Craik05f3d6e2014-06-02 16:27:04 -070066 hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
67 return JenkinsHashWhiten(hash);
68}
69
Chris Craik6ac174b2014-06-17 13:47:05 -070070void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const {
71 matrix->loadScale(scaleX, scaleY, 1.0f);
72 paint->setAntiAlias(aa);
73 paint->setStrokeCap(cap);
74 paint->setStyle(style);
75 paint->setStrokeWidth(strokeWidth);
76}
77
Chris Craik05f3d6e2014-06-02 16:27:04 -070078TessellationCache::ShadowDescription::ShadowDescription()
Chris Craikd41c4d82015-01-05 15:51:13 -080079 : nodeKey(nullptr) {
Chris Craik05f3d6e2014-06-02 16:27:04 -070080 memset(&matrixData, 0, 16 * sizeof(float));
81}
82
83TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform)
84 : nodeKey(nodeKey) {
85 memcpy(&matrixData, drawTransform->data, 16 * sizeof(float));
86}
87
88hash_t TessellationCache::ShadowDescription::hash() const {
89 uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*));
90 hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float));
91 return JenkinsHashWhiten(hash);
92}
93
94///////////////////////////////////////////////////////////////////////////////
95// General purpose tessellation task processing
96///////////////////////////////////////////////////////////////////////////////
97
98class TessellationCache::TessellationTask : public Task<VertexBuffer*> {
99public:
Chris Craik6ac174b2014-06-17 13:47:05 -0700100 TessellationTask(Tessellator tessellator, const Description& description)
Chris Craik05f3d6e2014-06-02 16:27:04 -0700101 : tessellator(tessellator)
Chris Craik6ac174b2014-06-17 13:47:05 -0700102 , description(description) {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700103 }
104
105 ~TessellationTask() {}
106
107 Tessellator tessellator;
108 Description description;
Chris Craik05f3d6e2014-06-02 16:27:04 -0700109};
110
111class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> {
112public:
113 TessellationProcessor(Caches& caches)
114 : TaskProcessor<VertexBuffer*>(&caches.tasks) {}
115 ~TessellationProcessor() {}
116
Chris Craikd41c4d82015-01-05 15:51:13 -0800117 virtual void onProcess(const sp<Task<VertexBuffer*> >& task) override {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700118 TessellationTask* t = static_cast<TessellationTask*>(task.get());
119 ATRACE_NAME("shape tessellation");
Chris Craik6ac174b2014-06-17 13:47:05 -0700120 VertexBuffer* buffer = t->tessellator(t->description);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700121 t->setResult(buffer);
122 }
123};
124
John Reck1aa5d2d2014-07-24 13:38:28 -0700125class TessellationCache::Buffer {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700126public:
127 Buffer(const sp<Task<VertexBuffer*> >& task)
128 : mTask(task)
Chris Craikd41c4d82015-01-05 15:51:13 -0800129 , mBuffer(nullptr) {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700130 }
131
132 ~Buffer() {
133 mTask.clear();
134 delete mBuffer;
135 }
136
137 unsigned int getSize() {
138 blockOnPrecache();
139 return mBuffer->getSize();
140 }
141
142 const VertexBuffer* getVertexBuffer() {
143 blockOnPrecache();
144 return mBuffer;
145 }
146
147private:
148 void blockOnPrecache() {
Chris Craikd41c4d82015-01-05 15:51:13 -0800149 if (mTask != nullptr) {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700150 mBuffer = mTask->getResult();
Chris Craikd41c4d82015-01-05 15:51:13 -0800151 LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "Failed to precache");
Chris Craik05f3d6e2014-06-02 16:27:04 -0700152 mTask.clear();
153 }
154 }
155 sp<Task<VertexBuffer*> > mTask;
156 VertexBuffer* mBuffer;
157};
158
159///////////////////////////////////////////////////////////////////////////////
160// Shadow tessellation task processing
161///////////////////////////////////////////////////////////////////////////////
162
Chris Craik05f3d6e2014-06-02 16:27:04 -0700163static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) {
164 // map z coordinate with true 3d matrix
165 point.z = transformZ->mapZ(point);
166
167 // map x,y coordinates with draw/Skia matrix
168 transformXY->mapPoint(point.x, point.y);
169}
170
Chris Craikfca52b752015-04-28 11:45:59 -0700171static void reverseVertexArray(Vertex* polygon, int len) {
172 int n = len / 2;
173 for (int i = 0; i < n; i++) {
174 Vertex tmp = polygon[i];
175 int k = len - 1 - i;
176 polygon[i] = polygon[k];
177 polygon[k] = tmp;
178 }
179}
180
John Reck82f5e0c2015-10-22 17:07:45 -0700181void tessellateShadows(
Chris Craik05f3d6e2014-06-02 16:27:04 -0700182 const Matrix4* drawTransform, const Rect* localClip,
183 bool isCasterOpaque, const SkPath* casterPerimeter,
184 const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
185 const Vector3& lightCenter, float lightRadius,
186 VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer) {
187
188 // tessellate caster outline into a 2d polygon
John Reck272a6852015-07-29 16:48:58 -0700189 std::vector<Vertex> casterVertices2d;
ztenghui21ef8202015-05-28 16:06:28 -0700190 const float casterRefinementThreshold = 2.0f;
Chris Craik05f3d6e2014-06-02 16:27:04 -0700191 PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
ztenghui21ef8202015-05-28 16:06:28 -0700192 casterRefinementThreshold, casterVertices2d);
Chris Craikfca52b752015-04-28 11:45:59 -0700193
194 // Shadow requires CCW for now. TODO: remove potential double-reverse
John Reck272a6852015-07-29 16:48:58 -0700195 reverseVertexArray(&casterVertices2d.front(), casterVertices2d.size());
Chris Craik05f3d6e2014-06-02 16:27:04 -0700196
197 if (casterVertices2d.size() == 0) return;
198
199 // map 2d caster poly into 3d
200 const int casterVertexCount = casterVertices2d.size();
201 Vector3 casterPolygon[casterVertexCount];
202 float minZ = FLT_MAX;
203 float maxZ = -FLT_MAX;
204 for (int i = 0; i < casterVertexCount; i++) {
205 const Vertex& point2d = casterVertices2d[i];
John Reck1aa5d2d2014-07-24 13:38:28 -0700206 casterPolygon[i] = (Vector3){point2d.x, point2d.y, 0};
Chris Craik05f3d6e2014-06-02 16:27:04 -0700207 mapPointFakeZ(casterPolygon[i], casterTransformXY, casterTransformZ);
Chris Craike6a15ee2015-07-07 18:42:17 -0700208 minZ = std::min(minZ, casterPolygon[i].z);
209 maxZ = std::max(maxZ, casterPolygon[i].z);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700210 }
211
212 // map the centroid of the caster into 3d
213 Vector2 centroid = ShadowTessellator::centroid2d(
John Reck272a6852015-07-29 16:48:58 -0700214 reinterpret_cast<const Vector2*>(&casterVertices2d.front()),
Chris Craik05f3d6e2014-06-02 16:27:04 -0700215 casterVertexCount);
John Reck1aa5d2d2014-07-24 13:38:28 -0700216 Vector3 centroid3d = {centroid.x, centroid.y, 0};
Chris Craik05f3d6e2014-06-02 16:27:04 -0700217 mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ);
218
219 // if the caster intersects the z=0 plane, lift it in Z so it doesn't
220 if (minZ < SHADOW_MIN_CASTER_Z) {
221 float casterLift = SHADOW_MIN_CASTER_Z - minZ;
222 for (int i = 0; i < casterVertexCount; i++) {
223 casterPolygon[i].z += casterLift;
224 }
225 centroid3d.z += casterLift;
226 }
227
228 // Check whether we want to draw the shadow at all by checking the caster's bounds against clip.
229 // We only have ortho projection, so we can just ignore the Z in caster for
230 // simple rejection calculation.
231 Rect casterBounds(casterPerimeter->getBounds());
232 casterTransformXY->mapRect(casterBounds);
233
234 // actual tessellation of both shadows
235 ShadowTessellator::tessellateAmbientShadow(
236 isCasterOpaque, casterPolygon, casterVertexCount, centroid3d,
237 casterBounds, *localClip, maxZ, ambientBuffer);
238
239 ShadowTessellator::tessellateSpotShadow(
ztenghuic50a03d2014-08-21 13:47:54 -0700240 isCasterOpaque, casterPolygon, casterVertexCount, centroid3d,
Chris Craik05f3d6e2014-06-02 16:27:04 -0700241 *drawTransform, lightCenter, lightRadius, casterBounds, *localClip,
242 spotBuffer);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700243}
244
245class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> {
246public:
247 ShadowProcessor(Caches& caches)
248 : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {}
249 ~ShadowProcessor() {}
250
Chris Craikd41c4d82015-01-05 15:51:13 -0800251 virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) override {
Chris Craik6e068c012016-01-15 16:15:30 -0800252 TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get());
Chris Craik05f3d6e2014-06-02 16:27:04 -0700253 ATRACE_NAME("shadow tessellation");
254
255 VertexBuffer* ambientBuffer = new VertexBuffer;
256 VertexBuffer* spotBuffer = new VertexBuffer;
Chris Craik1b3be082014-06-11 13:44:19 -0700257 tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
258 &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
Chris Craik05f3d6e2014-06-02 16:27:04 -0700259 *ambientBuffer, *spotBuffer);
260
261 t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer));
262 }
263};
264
265///////////////////////////////////////////////////////////////////////////////
266// Cache constructor/destructor
267///////////////////////////////////////////////////////////////////////////////
268
269TessellationCache::TessellationCache()
270 : mSize(0)
271 , mMaxSize(MB(DEFAULT_VERTEX_CACHE_SIZE))
272 , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity)
273 , mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) {
274 char property[PROPERTY_VALUE_MAX];
Chris Craikd41c4d82015-01-05 15:51:13 -0800275 if (property_get(PROPERTY_VERTEX_CACHE_SIZE, property, nullptr) > 0) {
Chris Craik11718bc2015-09-22 11:50:13 -0700276 INIT_LOGD(" Setting tessellation cache size to %sMB", property);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700277 setMaxSize(MB(atof(property)));
278 } else {
Chris Craik11718bc2015-09-22 11:50:13 -0700279 INIT_LOGD(" Using default tessellation cache size of %.2fMB", DEFAULT_VERTEX_CACHE_SIZE);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700280 }
281
282 mCache.setOnEntryRemovedListener(&mBufferRemovedListener);
283 mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener);
Chris Craik2507c342015-05-04 14:36:49 -0700284 mDebugEnabled = Properties::debugLevel & kDebugCaches;
Chris Craik05f3d6e2014-06-02 16:27:04 -0700285}
286
287TessellationCache::~TessellationCache() {
288 mCache.clear();
289}
290
291///////////////////////////////////////////////////////////////////////////////
292// Size management
293///////////////////////////////////////////////////////////////////////////////
294
295uint32_t TessellationCache::getSize() {
296 LruCache<Description, Buffer*>::Iterator iter(mCache);
297 uint32_t size = 0;
298 while (iter.next()) {
299 size += iter.value()->getSize();
300 }
301 return size;
302}
303
304uint32_t TessellationCache::getMaxSize() {
305 return mMaxSize;
306}
307
308void TessellationCache::setMaxSize(uint32_t maxSize) {
309 mMaxSize = maxSize;
310 while (mSize > mMaxSize) {
311 mCache.removeOldest();
312 }
313}
314
315///////////////////////////////////////////////////////////////////////////////
316// Caching
317///////////////////////////////////////////////////////////////////////////////
318
319
320void TessellationCache::trim() {
321 uint32_t size = getSize();
322 while (size > mMaxSize) {
323 size -= mCache.peekOldestValue()->getSize();
324 mCache.removeOldest();
325 }
326 mShadowCache.clear();
327}
328
329void TessellationCache::clear() {
330 mCache.clear();
331 mShadowCache.clear();
332}
333
334///////////////////////////////////////////////////////////////////////////////
335// Callbacks
336///////////////////////////////////////////////////////////////////////////////
337
Andreas Gampe64bb4132014-11-22 00:35:09 +0000338void TessellationCache::BufferRemovedListener::operator()(Description& description,
Chris Craik05f3d6e2014-06-02 16:27:04 -0700339 Buffer*& buffer) {
340 delete buffer;
341}
342
343///////////////////////////////////////////////////////////////////////////////
344// Shadows
345///////////////////////////////////////////////////////////////////////////////
346
347void TessellationCache::precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
348 bool opaque, const SkPath* casterPerimeter,
349 const Matrix4* transformXY, const Matrix4* transformZ,
350 const Vector3& lightCenter, float lightRadius) {
351 ShadowDescription key(casterPerimeter, drawTransform);
352
Mykola Kondratenkob1596332015-03-12 15:20:38 +0100353 if (mShadowCache.get(key)) return;
Chris Craik05f3d6e2014-06-02 16:27:04 -0700354 sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque,
355 casterPerimeter, transformXY, transformZ, lightCenter, lightRadius);
Chris Craikd41c4d82015-01-05 15:51:13 -0800356 if (mShadowProcessor == nullptr) {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700357 mShadowProcessor = new ShadowProcessor(Caches::getInstance());
358 }
Chris Craikdee66b62015-04-20 14:54:49 -0700359 mShadowProcessor->add(task);
Chris Craikd41c4d82015-01-05 15:51:13 -0800360 task->incStrong(nullptr); // not using sp<>s, so manually ref while in the cache
Chris Craik05f3d6e2014-06-02 16:27:04 -0700361 mShadowCache.put(key, task.get());
362}
363
364void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip,
365 bool opaque, const SkPath* casterPerimeter,
366 const Matrix4* transformXY, const Matrix4* transformZ,
367 const Vector3& lightCenter, float lightRadius, vertexBuffer_pair_t& outBuffers) {
368 ShadowDescription key(casterPerimeter, drawTransform);
369 ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key));
370 if (!task) {
371 precacheShadows(drawTransform, localClip, opaque, casterPerimeter,
372 transformXY, transformZ, lightCenter, lightRadius);
373 task = static_cast<ShadowTask*>(mShadowCache.get(key));
374 }
Chris Craikd41c4d82015-01-05 15:51:13 -0800375 LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
Chris Craik05f3d6e2014-06-02 16:27:04 -0700376 outBuffers = *(task->getResult());
377}
378
Chris Craik6e068c012016-01-15 16:15:30 -0800379sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask(
380 const Matrix4* drawTransform, const Rect& localClip,
381 bool opaque, const SkPath* casterPerimeter,
382 const Matrix4* transformXY, const Matrix4* transformZ,
383 const Vector3& lightCenter, float lightRadius) {
384 ShadowDescription key(casterPerimeter, drawTransform);
385 ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key));
386 if (!task) {
387 precacheShadows(drawTransform, localClip, opaque, casterPerimeter,
388 transformXY, transformZ, lightCenter, lightRadius);
389 task = static_cast<ShadowTask*>(mShadowCache.get(key));
390 }
391 LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
392 return task;
393}
394
395TessellationCache::ShadowTask::~ShadowTask() {
396 TessellationCache::vertexBuffer_pair_t* bufferPair = getResult();
397 delete bufferPair->getFirst();
398 delete bufferPair->getSecond();
399 delete bufferPair;
400}
401
Chris Craik05f3d6e2014-06-02 16:27:04 -0700402///////////////////////////////////////////////////////////////////////////////
403// Tessellation precaching
404///////////////////////////////////////////////////////////////////////////////
405
Chris Craik05f3d6e2014-06-02 16:27:04 -0700406TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(
Chris Craik6ac174b2014-06-17 13:47:05 -0700407 const Description& entry, Tessellator tessellator) {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700408 Buffer* buffer = mCache.get(entry);
409 if (!buffer) {
410 // not cached, enqueue a task to fill the buffer
Chris Craik6ac174b2014-06-17 13:47:05 -0700411 sp<TessellationTask> task = new TessellationTask(tessellator, entry);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700412 buffer = new Buffer(task);
413
Chris Craikd41c4d82015-01-05 15:51:13 -0800414 if (mProcessor == nullptr) {
Chris Craik05f3d6e2014-06-02 16:27:04 -0700415 mProcessor = new TessellationProcessor(Caches::getInstance());
416 }
Chris Craikdee66b62015-04-20 14:54:49 -0700417 mProcessor->add(task);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700418 mCache.put(entry, buffer);
419 }
420 return buffer;
421}
422
Chris Craik6ac174b2014-06-17 13:47:05 -0700423static VertexBuffer* tessellatePath(const TessellationCache::Description& description,
424 const SkPath& path) {
425 Matrix4 matrix;
426 SkPaint paint;
427 description.setupMatrixAndPaint(&matrix, &paint);
428 VertexBuffer* buffer = new VertexBuffer();
429 PathTessellator::tessellatePath(path, &paint, matrix, *buffer);
430 return buffer;
431}
432
Chris Craik05f3d6e2014-06-02 16:27:04 -0700433///////////////////////////////////////////////////////////////////////////////
Chris Craik6ac174b2014-06-17 13:47:05 -0700434// RoundRect
Chris Craik05f3d6e2014-06-02 16:27:04 -0700435///////////////////////////////////////////////////////////////////////////////
436
Chris Craik6ac174b2014-06-17 13:47:05 -0700437static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) {
438 SkRect rect = SkRect::MakeWH(description.shape.roundRect.width,
439 description.shape.roundRect.height);
440 float rx = description.shape.roundRect.rx;
441 float ry = description.shape.roundRect.ry;
442 if (description.style == SkPaint::kStrokeAndFill_Style) {
443 float outset = description.strokeWidth / 2;
Chris Craik05f3d6e2014-06-02 16:27:04 -0700444 rect.outset(outset, outset);
445 rx += outset;
446 ry += outset;
447 }
448 SkPath path;
449 path.addRoundRect(rect, rx, ry);
Chris Craik6ac174b2014-06-17 13:47:05 -0700450 return tessellatePath(description, path);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700451}
452
Chris Craik6ac174b2014-06-17 13:47:05 -0700453TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(
454 const Matrix4& transform, const SkPaint& paint,
455 float width, float height, float rx, float ry) {
456 Description entry(Description::kRoundRect, transform, paint);
457 entry.shape.roundRect.width = width;
458 entry.shape.roundRect.height = height;
459 entry.shape.roundRect.rx = rx;
460 entry.shape.roundRect.ry = ry;
461 return getOrCreateBuffer(entry, &tessellateRoundRect);
Chris Craik05f3d6e2014-06-02 16:27:04 -0700462}
Chris Craik6ac174b2014-06-17 13:47:05 -0700463const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint,
464 float width, float height, float rx, float ry) {
465 return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer();
Chris Craik05f3d6e2014-06-02 16:27:04 -0700466}
467
468}; // namespace uirenderer
469}; // namespace android