/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <utils/JenkinsHash.h>
#include <utils/Trace.h>
#include "Caches.h"
#include "OpenGLRenderer.h"
#include "PathTessellator.h"
#include "ShadowTessellator.h"
#include "TessellationCache.h"
#include "thread/Signal.h"
#include "thread/Task.h"
#include "thread/TaskProcessor.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Cache entries
///////////////////////////////////////////////////////////////////////////////
TessellationCache::Description::Description()
: type(Type::None)
, scaleX(1.0f)
, scaleY(1.0f)
, aa(false)
, cap(SkPaint::kDefault_Cap)
, style(SkPaint::kFill_Style)
, strokeWidth(1.0f) {
// Shape bits should be set to zeroes, because they are used for hash calculation.
memset(&shape, 0, sizeof(Shape));
}
TessellationCache::Description::Description(Type type, const Matrix4& transform, const SkPaint& paint)
: type(type)
, aa(paint.isAntiAlias())
, cap(paint.getStrokeCap())
, style(paint.getStyle())
, strokeWidth(paint.getStrokeWidth()) {
PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
// Shape bits should be set to zeroes, because they are used for hash calculation.
memset(&shape, 0, sizeof(Shape));
}
bool TessellationCache::Description::operator==(const TessellationCache::Description& rhs) const {
if (type != rhs.type) return false;
if (scaleX != rhs.scaleX) return false;
if (scaleY != rhs.scaleY) return false;
if (aa != rhs.aa) return false;
if (cap != rhs.cap) return false;
if (style != rhs.style) return false;
if (strokeWidth != rhs.strokeWidth) return false;
if (type == Type::None) return true;
const Shape::RoundRect& lRect = shape.roundRect;
const Shape::RoundRect& rRect = rhs.shape.roundRect;
if (lRect.width != rRect.width) return false;
if (lRect.height != rRect.height) return false;
if (lRect.rx != rRect.rx) return false;
return lRect.ry == rRect.ry;
}
hash_t TessellationCache::Description::hash() const {
uint32_t hash = JenkinsHashMix(0, static_cast<int>(type));
hash = JenkinsHashMix(hash, aa);
hash = JenkinsHashMix(hash, cap);
hash = JenkinsHashMix(hash, style);
hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
hash = JenkinsHashMix(hash, android::hash_type(scaleX));
hash = JenkinsHashMix(hash, android::hash_type(scaleY));
hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
return JenkinsHashWhiten(hash);
}
void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const {
matrix->loadScale(scaleX, scaleY, 1.0f);
paint->setAntiAlias(aa);
paint->setStrokeCap(cap);
paint->setStyle(style);
paint->setStrokeWidth(strokeWidth);
}
TessellationCache::ShadowDescription::ShadowDescription()
: nodeKey(nullptr) {
memset(&matrixData, 0, sizeof(matrixData));
}
TessellationCache::ShadowDescription::ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform)
: nodeKey(nodeKey) {
memcpy(&matrixData, drawTransform->data, sizeof(matrixData));
}
bool TessellationCache::ShadowDescription::operator==(
const TessellationCache::ShadowDescription& rhs) const {
return nodeKey == rhs.nodeKey
&& memcmp(&matrixData, &rhs.matrixData, sizeof(matrixData)) == 0;
}
hash_t TessellationCache::ShadowDescription::hash() const {
uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*));
hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, sizeof(matrixData));
return JenkinsHashWhiten(hash);
}
///////////////////////////////////////////////////////////////////////////////
// General purpose tessellation task processing
///////////////////////////////////////////////////////////////////////////////
class TessellationCache::TessellationTask : public Task<VertexBuffer*> {
public:
TessellationTask(Tessellator tessellator, const Description& description)
: tessellator(tessellator)
, description(description) {
}
~TessellationTask() {}
Tessellator tessellator;
Description description;
};
class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> {
public:
TessellationProcessor(Caches& caches)
: TaskProcessor<VertexBuffer*>(&caches.tasks) {}
~TessellationProcessor() {}
virtual void onProcess(const sp<Task<VertexBuffer*> >& task) override {
TessellationTask* t = static_cast<TessellationTask*>(task.get());
ATRACE_NAME("shape tessellation");
VertexBuffer* buffer = t->tessellator(t->description);
t->setResult(buffer);
}
};
class TessellationCache::Buffer {
public:
Buffer(const sp<Task<VertexBuffer*> >& task)
: mTask(task)
, mBuffer(nullptr) {
}
~Buffer() {
mTask.clear();
delete mBuffer;
}
unsigned int getSize() {
blockOnPrecache();
return mBuffer->getSize();
}
const VertexBuffer* getVertexBuffer() {
blockOnPrecache();
return mBuffer;
}
private:
void blockOnPrecache() {
if (mTask != nullptr) {
mBuffer = mTask->getResult();
LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "Failed to precache");
mTask.clear();
}
}
sp<Task<VertexBuffer*> > mTask;
VertexBuffer* mBuffer;
};
///////////////////////////////////////////////////////////////////////////////
// Shadow tessellation task processing
///////////////////////////////////////////////////////////////////////////////
static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) {
// map z coordinate with true 3d matrix
point.z = transformZ->mapZ(point);
// map x,y coordinates with draw/Skia matrix
transformXY->mapPoint(point.x, point.y);
}
static void reverseVertexArray(Vertex* polygon, int len) {
int n = len / 2;
for (int i = 0; i < n; i++) {
Vertex tmp = polygon[i];
int k = len - 1 - i;
polygon[i] = polygon[k];
polygon[k] = tmp;
}
}
void tessellateShadows(
const Matrix4* drawTransform, const Rect* localClip,
bool isCasterOpaque, const SkPath* casterPerimeter,
const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
const Vector3& lightCenter, float lightRadius,
VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer) {
// tessellate caster outline into a 2d polygon
std::vector<Vertex> casterVertices2d;
const float casterRefinementThreshold = 2.0f;
PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
casterRefinementThreshold, casterVertices2d);
// Shadow requires CCW for now. TODO: remove potential double-reverse
reverseVertexArray(&casterVertices2d.front(), casterVertices2d.size());
if (casterVertices2d.size() == 0) return;
// map 2d caster poly into 3d
const int casterVertexCount = casterVertices2d.size();
Vector3 casterPolygon[casterVertexCount];
float minZ = FLT_MAX;
float maxZ = -FLT_MAX;
for (int i = 0; i < casterVertexCount; i++) {
const Vertex& point2d = casterVertices2d[i];
casterPolygon[i] = (Vector3){point2d.x, point2d.y, 0};
mapPointFakeZ(casterPolygon[i], casterTransformXY, casterTransformZ);
minZ = std::min(minZ, casterPolygon[i].z);
maxZ = std::max(maxZ, casterPolygon[i].z);
}
// map the centroid of the caster into 3d
Vector2 centroid = ShadowTessellator::centroid2d(
reinterpret_cast<const Vector2*>(&casterVertices2d.front()),
casterVertexCount);
Vector3 centroid3d = {centroid.x, centroid.y, 0};
mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ);
// if the caster intersects the z=0 plane, lift it in Z so it doesn't
if (minZ < SHADOW_MIN_CASTER_Z) {
float casterLift = SHADOW_MIN_CASTER_Z - minZ;
for (int i = 0; i < casterVertexCount; i++) {
casterPolygon[i].z += casterLift;
}
centroid3d.z += casterLift;
}
// Check whether we want to draw the shadow at all by checking the caster's bounds against clip.
// We only have ortho projection, so we can just ignore the Z in caster for
// simple rejection calculation.
Rect casterBounds(casterPerimeter->getBounds());
casterTransformXY->mapRect(casterBounds);
// actual tessellation of both shadows
ShadowTessellator::tessellateAmbientShadow(
isCasterOpaque, casterPolygon, casterVertexCount, centroid3d,
casterBounds, *localClip, maxZ, ambientBuffer);
ShadowTessellator::tessellateSpotShadow(
isCasterOpaque, casterPolygon, casterVertexCount, centroid3d,
*drawTransform, lightCenter, lightRadius, casterBounds, *localClip,
spotBuffer);
}
class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t> {
public:
ShadowProcessor(Caches& caches)
: TaskProcessor<TessellationCache::vertexBuffer_pair_t>(&caches.tasks) {}
~ShadowProcessor() {}
virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t> >& task) override {
TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get());
ATRACE_NAME("shadow tessellation");
tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
&t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
t->ambientBuffer, t->spotBuffer);
t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer));
}
};
///////////////////////////////////////////////////////////////////////////////
// Cache constructor/destructor
///////////////////////////////////////////////////////////////////////////////
TessellationCache::TessellationCache()
: mMaxSize(Properties::tessellationCacheSize)
, mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity)
, mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) {
mCache.setOnEntryRemovedListener(&mBufferRemovedListener);
mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener);
mDebugEnabled = Properties::debugLevel & kDebugCaches;
}
TessellationCache::~TessellationCache() {
mCache.clear();
}
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
uint32_t TessellationCache::getSize() {
LruCache<Description, Buffer*>::Iterator iter(mCache);
uint32_t size = 0;
while (iter.next()) {
size += iter.value()->getSize();
}
return size;
}
uint32_t TessellationCache::getMaxSize() {
return mMaxSize;
}
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
void TessellationCache::trim() {
uint32_t size = getSize();
while (size > mMaxSize) {
size -= mCache.peekOldestValue()->getSize();
mCache.removeOldest();
}
mShadowCache.clear();
}
void TessellationCache::clear() {
mCache.clear();
mShadowCache.clear();
}
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
void TessellationCache::BufferRemovedListener::operator()(Description& description,
Buffer*& buffer) {
delete buffer;
}
///////////////////////////////////////////////////////////////////////////////
// Shadows
///////////////////////////////////////////////////////////////////////////////
void TessellationCache::precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
bool opaque, const SkPath* casterPerimeter,
const Matrix4* transformXY, const Matrix4* transformZ,
const Vector3& lightCenter, float lightRadius) {
ShadowDescription key(casterPerimeter, drawTransform);
if (mShadowCache.get(key)) return;
sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque,
casterPerimeter, transformXY, transformZ, lightCenter, lightRadius);
if (mShadowProcessor == nullptr) {
mShadowProcessor = new ShadowProcessor(Caches::getInstance());
}
mShadowProcessor->add(task);
task->incStrong(nullptr); // not using sp<>s, so manually ref while in the cache
mShadowCache.put(key, task.get());
}
void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip,
bool opaque, const SkPath* casterPerimeter,
const Matrix4* transformXY, const Matrix4* transformZ,
const Vector3& lightCenter, float lightRadius, vertexBuffer_pair_t& outBuffers) {
ShadowDescription key(casterPerimeter, drawTransform);
ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key));
if (!task) {
precacheShadows(drawTransform, localClip, opaque, casterPerimeter,
transformXY, transformZ, lightCenter, lightRadius);
task = static_cast<ShadowTask*>(mShadowCache.get(key));
}
LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
outBuffers = task->getResult();
}
sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask(
const Matrix4* drawTransform, const Rect& localClip,
bool opaque, const SkPath* casterPerimeter,
const Matrix4* transformXY, const Matrix4* transformZ,
const Vector3& lightCenter, float lightRadius) {
ShadowDescription key(casterPerimeter, drawTransform);
ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key));
if (!task) {
precacheShadows(drawTransform, localClip, opaque, casterPerimeter,
transformXY, transformZ, lightCenter, lightRadius);
task = static_cast<ShadowTask*>(mShadowCache.get(key));
}
LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
return task;
}
///////////////////////////////////////////////////////////////////////////////
// Tessellation precaching
///////////////////////////////////////////////////////////////////////////////
TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(
const Description& entry, Tessellator tessellator) {
Buffer* buffer = mCache.get(entry);
if (!buffer) {
// not cached, enqueue a task to fill the buffer
sp<TessellationTask> task = new TessellationTask(tessellator, entry);
buffer = new Buffer(task);
if (mProcessor == nullptr) {
mProcessor = new TessellationProcessor(Caches::getInstance());
}
mProcessor->add(task);
mCache.put(entry, buffer);
}
return buffer;
}
static VertexBuffer* tessellatePath(const TessellationCache::Description& description,
const SkPath& path) {
Matrix4 matrix;
SkPaint paint;
description.setupMatrixAndPaint(&matrix, &paint);
VertexBuffer* buffer = new VertexBuffer();
PathTessellator::tessellatePath(path, &paint, matrix, *buffer);
return buffer;
}
///////////////////////////////////////////////////////////////////////////////
// RoundRect
///////////////////////////////////////////////////////////////////////////////
static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) {
SkRect rect = SkRect::MakeWH(description.shape.roundRect.width,
description.shape.roundRect.height);
float rx = description.shape.roundRect.rx;
float ry = description.shape.roundRect.ry;
if (description.style == SkPaint::kStrokeAndFill_Style) {
float outset = description.strokeWidth / 2;
rect.outset(outset, outset);
rx += outset;
ry += outset;
}
SkPath path;
path.addRoundRect(rect, rx, ry);
return tessellatePath(description, path);
}
TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(
const Matrix4& transform, const SkPaint& paint,
float width, float height, float rx, float ry) {
Description entry(Description::Type::RoundRect, transform, paint);
entry.shape.roundRect.width = width;
entry.shape.roundRect.height = height;
entry.shape.roundRect.rx = rx;
entry.shape.roundRect.ry = ry;
return getOrCreateBuffer(entry, &tessellateRoundRect);
}
const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint,
float width, float height, float rx, float ry) {
return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer();
}
}; // namespace uirenderer
}; // namespace android