Get rid of compuation of the intersection for penumbra and convex hull for umbra. Use simple circle / normal to compute the penumbra and simple intersection for umbra. The new way could be 2x to 4x faster from rectangle to round shape. And this part is roughly half of the shadow computation, or 2/3 of spot shadow computation. This improve the spot shadow spikeness too. b/16712006 b/14976551 Change-Id: I02911784868731369efa73f76fc915bc08248600
477 lines
17 KiB
C++
477 lines
17 KiB
C++
/*
|
|
* 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(kNone)
|
|
, scaleX(1.0f)
|
|
, scaleY(1.0f)
|
|
, aa(false)
|
|
, cap(SkPaint::kDefault_Cap)
|
|
, style(SkPaint::kFill_Style)
|
|
, strokeWidth(1.0f) {
|
|
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);
|
|
memset(&shape, 0, sizeof(Shape));
|
|
}
|
|
|
|
hash_t TessellationCache::Description::hash() const {
|
|
uint32_t hash = JenkinsHashMix(0, 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(NULL) {
|
|
memset(&matrixData, 0, 16 * sizeof(float));
|
|
}
|
|
|
|
TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform)
|
|
: nodeKey(nodeKey) {
|
|
memcpy(&matrixData, drawTransform->data, 16 * sizeof(float));
|
|
}
|
|
|
|
hash_t TessellationCache::ShadowDescription::hash() const {
|
|
uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*));
|
|
hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float));
|
|
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) {
|
|
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(NULL) {
|
|
}
|
|
|
|
~Buffer() {
|
|
mTask.clear();
|
|
delete mBuffer;
|
|
}
|
|
|
|
unsigned int getSize() {
|
|
blockOnPrecache();
|
|
return mBuffer->getSize();
|
|
}
|
|
|
|
const VertexBuffer* getVertexBuffer() {
|
|
blockOnPrecache();
|
|
return mBuffer;
|
|
}
|
|
|
|
private:
|
|
void blockOnPrecache() {
|
|
if (mTask != NULL) {
|
|
mBuffer = mTask->getResult();
|
|
LOG_ALWAYS_FATAL_IF(mBuffer == NULL, "Failed to precache");
|
|
mTask.clear();
|
|
}
|
|
}
|
|
sp<Task<VertexBuffer*> > mTask;
|
|
VertexBuffer* mBuffer;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Shadow tessellation task processing
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> {
|
|
public:
|
|
ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque,
|
|
const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ,
|
|
const Vector3& lightCenter, float lightRadius)
|
|
: drawTransform(*drawTransform)
|
|
, localClip(localClip)
|
|
, opaque(opaque)
|
|
, casterPerimeter(*casterPerimeter)
|
|
, transformXY(*transformXY)
|
|
, transformZ(*transformZ)
|
|
, lightCenter(lightCenter)
|
|
, lightRadius(lightRadius) {
|
|
}
|
|
|
|
~ShadowTask() {
|
|
TessellationCache::vertexBuffer_pair_t* bufferPair = getResult();
|
|
delete bufferPair->getFirst();
|
|
delete bufferPair->getSecond();
|
|
delete bufferPair;
|
|
}
|
|
|
|
/* Note - we deep copy all task parameters, because *even though* pointers into Allocator
|
|
* controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame,
|
|
* certain Allocators are destroyed before trim() is called to flush incomplete tasks.
|
|
*
|
|
* These deep copies could be avoided, long term, by cancelling or flushing outstanding tasks
|
|
* before tearning down single-frame LinearAllocators.
|
|
*/
|
|
const Matrix4 drawTransform;
|
|
const Rect localClip;
|
|
bool opaque;
|
|
const SkPath casterPerimeter;
|
|
const Matrix4 transformXY;
|
|
const Matrix4 transformZ;
|
|
const Vector3 lightCenter;
|
|
const float lightRadius;
|
|
};
|
|
|
|
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 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
|
|
Vector<Vertex> casterVertices2d;
|
|
const float casterRefinementThresholdSquared = 20.0f; // TODO: experiment with this value
|
|
PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
|
|
casterRefinementThresholdSquared, casterVertices2d);
|
|
if (!ShadowTessellator::isClockwisePath(*casterPerimeter)) {
|
|
ShadowTessellator::reverseVertexArray(casterVertices2d.editArray(),
|
|
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 = fmin(minZ, casterPolygon[i].z);
|
|
maxZ = fmax(maxZ, casterPolygon[i].z);
|
|
}
|
|
|
|
// map the centroid of the caster into 3d
|
|
Vector2 centroid = ShadowTessellator::centroid2d(
|
|
reinterpret_cast<const Vector2*>(casterVertices2d.array()),
|
|
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) {
|
|
ShadowTask* t = static_cast<ShadowTask*>(task.get());
|
|
ATRACE_NAME("shadow tessellation");
|
|
|
|
VertexBuffer* ambientBuffer = new VertexBuffer;
|
|
VertexBuffer* spotBuffer = new VertexBuffer;
|
|
tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
|
|
&t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
|
|
*ambientBuffer, *spotBuffer);
|
|
|
|
t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer));
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Cache constructor/destructor
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TessellationCache::TessellationCache()
|
|
: mSize(0)
|
|
, mMaxSize(MB(DEFAULT_VERTEX_CACHE_SIZE))
|
|
, mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity)
|
|
, mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) {
|
|
char property[PROPERTY_VALUE_MAX];
|
|
if (property_get(PROPERTY_VERTEX_CACHE_SIZE, property, NULL) > 0) {
|
|
INIT_LOGD(" Setting %s cache size to %sMB", name, property);
|
|
setMaxSize(MB(atof(property)));
|
|
} else {
|
|
INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_VERTEX_CACHE_SIZE);
|
|
}
|
|
|
|
mCache.setOnEntryRemovedListener(&mBufferRemovedListener);
|
|
mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener);
|
|
mDebugEnabled = readDebugLevel() & 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;
|
|
}
|
|
|
|
void TessellationCache::setMaxSize(uint32_t maxSize) {
|
|
mMaxSize = maxSize;
|
|
while (mSize > mMaxSize) {
|
|
mCache.removeOldest();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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);
|
|
|
|
sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque,
|
|
casterPerimeter, transformXY, transformZ, lightCenter, lightRadius);
|
|
if (mShadowProcessor == NULL) {
|
|
mShadowProcessor = new ShadowProcessor(Caches::getInstance());
|
|
}
|
|
mShadowProcessor->add(task);
|
|
|
|
task->incStrong(NULL); // 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 == NULL, "shadow not precached");
|
|
outBuffers = *(task->getResult());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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 == NULL) {
|
|
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::kRoundRect, 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
|