Tessellate on worker threads

Tessellate and cache (where possible) shadow and round rect
tessellation tasks.

Change-Id: I2cfda8e11d83d51ea74af871235cf26e8f831d40
This commit is contained in:
Chris Craik
2014-06-02 16:27:04 -07:00
parent faecafce62
commit 05f3d6e511
26 changed files with 867 additions and 213 deletions

View File

@@ -44,18 +44,18 @@ namespace uirenderer {
* @param shadowVertexBuffer Return an floating point array of (x, y, a)
* triangle strips mode.
*/
VertexBufferMode AmbientShadow::createAmbientShadow(bool isCasterOpaque,
void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
const Vector3* vertices, int vertexCount, const Vector3& centroid3d,
float heightFactor, float geomFactor, VertexBuffer& shadowVertexBuffer) {
const int rays = SHADOW_RAY_COUNT;
VertexBufferMode mode = kVertexBufferMode_OnePolyRingShadow;
VertexBuffer::Mode mode = VertexBuffer::kOnePolyRingShadow;
// Validate the inputs.
if (vertexCount < 3 || heightFactor <= 0 || rays <= 0
|| geomFactor <= 0) {
#if DEBUG_SHADOW
ALOGW("Invalid input for createAmbientShadow(), early return!");
#endif
return mode; // vertex buffer is empty, so any mode doesn't matter.
return;
}
Vector<Vector2> dir; // TODO: use C++11 unique_ptr
@@ -127,7 +127,7 @@ VertexBufferMode AmbientShadow::createAmbientShadow(bool isCasterOpaque,
// If caster isn't opaque, we need to to fill the umbra by storing the umbra's
// centroid in the innermost ring of vertices.
if (!isCasterOpaque) {
mode = kVertexBufferMode_TwoPolyRingShadow;
mode = VertexBuffer::kTwoPolyRingShadow;
float centroidAlpha = 1.0 / (1 + centroid3d.z * heightFactor);
AlphaVertex centroidXYA;
AlphaVertex::set(&centroidXYA, centroid2d.x, centroid2d.y, centroidAlpha);
@@ -135,6 +135,7 @@ VertexBufferMode AmbientShadow::createAmbientShadow(bool isCasterOpaque,
shadowVertices[2 * rays + rayIndex] = centroidXYA;
}
}
shadowVertexBuffer.setMode(mode);
#if DEBUG_SHADOW
for (int i = 0; i < SHADOW_VERTEX_COUNT; i++) {
@@ -142,7 +143,6 @@ VertexBufferMode AmbientShadow::createAmbientShadow(bool isCasterOpaque,
shadowVertices[i].y, shadowVertices[i].alpha);
}
#endif
return mode;
}
/**

View File

@@ -35,7 +35,7 @@ namespace uirenderer {
*/
class AmbientShadow {
public:
static VertexBufferMode createAmbientShadow(bool isCasterOpaque, const Vector3* poly,
static void createAmbientShadow(bool isCasterOpaque, const Vector3* poly,
int polyLength, const Vector3& centroid3d, float heightFactor,
float geomFactor, VertexBuffer& shadowVertexBuffer);

View File

@@ -51,6 +51,7 @@ ifeq ($(USE_OPENGL_RENDERER),true)
SpotShadow.cpp \
StatefulBaseRenderer.cpp \
Stencil.cpp \
TessellationCache.cpp \
Texture.cpp \
TextureCache.cpp \
TextDropShadowCache.cpp

View File

@@ -273,6 +273,8 @@ void Caches::dumpMemoryUsage(String8 &log) {
gradientCache.getSize(), gradientCache.getMaxSize());
log.appendFormat(" PathCache %8d / %8d\n",
pathCache.getSize(), pathCache.getMaxSize());
log.appendFormat(" TessellationCache %8d / %8d\n",
tessellationCache.getSize(), tessellationCache.getMaxSize());
log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(),
dropShadowCache.getMaxSize());
log.appendFormat(" PatchCache %8d / %8d\n",
@@ -295,6 +297,7 @@ void Caches::dumpMemoryUsage(String8 &log) {
total += renderBufferCache.getSize();
total += gradientCache.getSize();
total += pathCache.getSize();
total += tessellationCache.getSize();
total += dropShadowCache.getSize();
total += patchCache.getSize();
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
@@ -358,6 +361,7 @@ void Caches::flush(FlushMode mode) {
fontRenderer->flush();
textureCache.flush();
pathCache.clear();
tessellationCache.clear();
// fall through
case kFlushMode_Layers:
layerCache.clear();

View File

@@ -43,6 +43,7 @@
#include "PatchCache.h"
#include "ProgramCache.h"
#include "PathCache.h"
#include "TessellationCache.h"
#include "TextDropShadowCache.h"
#include "FboCache.h"
#include "ResourceCache.h"
@@ -326,6 +327,7 @@ public:
ProgramCache programCache;
PathCache pathCache;
PatchCache patchCache;
TessellationCache tessellationCache;
TextDropShadowCache dropShadowCache;
FboCache fboCache;
ResourceCache resourceCache;

View File

@@ -1114,6 +1114,15 @@ public:
OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
}
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) {
DrawStrokableOp::onDefer(renderer, deferInfo, state);
if (!mPaint->getPathEffect()) {
renderer.getCaches().tessellationCache.precacheRoundRect(state.mMatrix,
mLocalBounds.getWidth(), mLocalBounds.getHeight(), mRx, mRy, mPaint);
}
}
virtual const char* name() { return "DrawRoundRect"; }
private:
@@ -1533,9 +1542,23 @@ public:
}
}
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) {
renderer.getCaches().tessellationCache.precacheShadows(&state.mMatrix,
renderer.getLocalClipBounds(), isCasterOpaque(), &mOutline,
&mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius());
}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
return renderer.drawShadow(mTransformXY, mTransformZ,
mCasterAlpha, mCasterUnclipped, &mOutline);
TessellationCache::vertexBuffer_pair_t buffers;
Matrix4 drawTransform;
renderer.getMatrix(&drawTransform);
renderer.getCaches().tessellationCache.getShadowBuffers(&drawTransform,
renderer.getLocalClipBounds(), isCasterOpaque(), &mOutline,
&mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius(),
buffers);
return renderer.drawShadow(mCasterAlpha, buffers.first, buffers.second);
}
virtual void output(int level, uint32_t logFlags) const {
@@ -1545,6 +1568,8 @@ public:
virtual const char* name() { return "DrawShadow"; }
private:
bool isCasterOpaque() { return mCasterAlpha >= 1.0f && mCasterUnclipped; }
const mat4 mTransformXY;
const mat4 mTransformZ;
const float mCasterAlpha;

View File

@@ -287,6 +287,7 @@ void OpenGLRenderer::finish() {
// of the current frame
if (getTargetFbo() == 0) {
mCaches.pathCache.trim();
mCaches.tessellationCache.trim();
}
if (!suppressErrorChecks()) {
@@ -2390,7 +2391,7 @@ status_t OpenGLRenderer::drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry*
return DrawGlInfo::kStatusDrew;
}
status_t OpenGLRenderer::drawVertexBuffer(VertexBufferMode mode,
status_t OpenGLRenderer::drawVertexBuffer(float translateX, float translateY,
const VertexBuffer& vertexBuffer, const SkPaint* paint, bool useOffset) {
// not missing call to quickReject/dirtyLayer, always done at a higher level
if (!vertexBuffer.getVertexCount()) {
@@ -2398,6 +2399,9 @@ status_t OpenGLRenderer::drawVertexBuffer(VertexBufferMode mode,
return DrawGlInfo::kStatusDone;
}
const Rect& bounds = vertexBuffer.getBounds();
dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
int color = paint->getColor();
bool isAA = paint->isAntiAlias();
@@ -2409,7 +2413,7 @@ status_t OpenGLRenderer::drawVertexBuffer(VertexBufferMode mode,
setupDrawShader(getShader(paint));
setupDrawBlending(paint, isAA);
setupDrawProgram();
setupDrawModelView(kModelViewMode_Translate, useOffset, 0, 0, 0, 0);
setupDrawModelView(kModelViewMode_Translate, useOffset, translateX, translateY, 0, 0);
setupDrawColorUniforms(getShader(paint));
setupDrawColorFilterUniforms(getColorFilter(paint));
setupDrawShaderUniforms(getShader(paint));
@@ -2429,13 +2433,14 @@ status_t OpenGLRenderer::drawVertexBuffer(VertexBufferMode mode,
glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords);
}
if (mode == kVertexBufferMode_Standard) {
const VertexBuffer::Mode mode = vertexBuffer.getMode();
if (mode == VertexBuffer::kStandard) {
mCaches.unbindIndicesBuffer();
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount());
} else if (mode == kVertexBufferMode_OnePolyRingShadow) {
} else if (mode == VertexBuffer::kOnePolyRingShadow) {
mCaches.bindShadowIndicesBuffer();
glDrawElements(GL_TRIANGLE_STRIP, ONE_POLY_RING_SHADOW_INDEX_COUNT, GL_UNSIGNED_SHORT, 0);
} else if (mode == kVertexBufferMode_TwoPolyRingShadow) {
} else if (mode == VertexBuffer::kTwoPolyRingShadow) {
mCaches.bindShadowIndicesBuffer();
glDrawElements(GL_TRIANGLE_STRIP, TWO_POLY_RING_SHADOW_INDEX_COUNT, GL_UNSIGNED_SHORT, 0);
}
@@ -2460,14 +2465,7 @@ status_t OpenGLRenderer::drawConvexPath(const SkPath& path, const SkPaint* paint
VertexBuffer vertexBuffer;
// TODO: try clipping large paths to viewport
PathTessellator::tessellatePath(path, paint, *currentTransform(), vertexBuffer);
if (hasLayer()) {
SkRect bounds = path.getBounds();
PathTessellator::expandBoundsForStroke(bounds, paint);
dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *currentTransform());
}
return drawVertexBuffer(kVertexBufferMode_Standard, vertexBuffer, paint);
return drawVertexBuffer(vertexBuffer, paint);
}
/**
@@ -2487,18 +2485,15 @@ status_t OpenGLRenderer::drawLines(const float* points, int count, const SkPaint
count &= ~0x3; // round down to nearest four
VertexBuffer buffer;
SkRect bounds;
PathTessellator::tessellateLines(points, count, paint, *currentTransform(), bounds, buffer);
PathTessellator::tessellateLines(points, count, paint, *currentTransform(), buffer);
const Rect& bounds = buffer.getBounds();
// can't pass paint, since style would be checked for outset. outset done by tessellation.
if (quickRejectSetupScissor(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) {
if (quickRejectSetupScissor(bounds.left, bounds.top, bounds.right, bounds.bottom)) {
return DrawGlInfo::kStatusDone;
}
dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *currentTransform());
bool useOffset = !paint->isAntiAlias();
return drawVertexBuffer(kVertexBufferMode_Standard, buffer, paint, useOffset);
return drawVertexBuffer(buffer, paint, useOffset);
}
status_t OpenGLRenderer::drawPoints(const float* points, int count, const SkPaint* paint) {
@@ -2507,18 +2502,15 @@ status_t OpenGLRenderer::drawPoints(const float* points, int count, const SkPain
count &= ~0x1; // round down to nearest two
VertexBuffer buffer;
SkRect bounds;
PathTessellator::tessellatePoints(points, count, paint, *currentTransform(), bounds, buffer);
PathTessellator::tessellatePoints(points, count, paint, *currentTransform(), buffer);
// can't pass paint, since style would be checked for outset. outset done by tessellation.
if (quickRejectSetupScissor(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) {
const Rect& bounds = buffer.getBounds();
if (quickRejectSetupScissor(bounds.left, bounds.top, bounds.right, bounds.bottom)) {
return DrawGlInfo::kStatusDone;
}
dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *currentTransform());
bool useOffset = !paint->isAntiAlias();
return drawVertexBuffer(kVertexBufferMode_Standard, buffer, paint, useOffset);
return drawVertexBuffer(buffer, paint, useOffset);
}
status_t OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
@@ -2564,16 +2556,9 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float
return drawShape(left, top, texture, p);
}
SkPath path;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
float outset = p->getStrokeWidth() / 2;
rect.outset(outset, outset);
rx += outset;
ry += outset;
}
path.addRoundRect(rect, rx, ry);
return drawConvexPath(path, p);
const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect(*currentTransform(),
right - left, bottom - top, rx, ry, p);
return drawVertexBuffer(left, top, *vertexBuffer, p);
}
status_t OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p) {
@@ -3192,8 +3177,8 @@ static void mapPointFakeZ(Vector3& point, const mat4& transformXY, const mat4& t
transformXY.mapPoint(point.x, point.y);
}
status_t OpenGLRenderer::drawShadow(const mat4& casterTransformXY, const mat4& casterTransformZ,
float casterAlpha, bool casterUnclipped, const SkPath* casterPerimeter) {
status_t OpenGLRenderer::drawShadow(float casterAlpha,
const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
if (currentSnapshot()->isIgnored()) return DrawGlInfo::kStatusDone;
// TODO: use quickRejectWithScissor. For now, always force enable scissor.
@@ -3202,77 +3187,14 @@ status_t OpenGLRenderer::drawShadow(const mat4& casterTransformXY, const mat4& c
SkPaint paint;
paint.setAntiAlias(true); // want to use AlphaVertex
// 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) {
// empty caster polygon computed from path
return DrawGlInfo::kStatusDone;
}
// 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 localClip = mSnapshot->getLocalClip();
Rect casterBounds(casterPerimeter->getBounds());
casterTransformXY.mapRect(casterBounds);
bool isCasterOpaque = (casterAlpha == 1.0f) && casterUnclipped;
// draw caster's shadows
if (mCaches.propertyAmbientShadowStrength > 0) {
if (ambientShadowVertexBuffer && mCaches.propertyAmbientShadowStrength > 0) {
paint.setARGB(casterAlpha * mCaches.propertyAmbientShadowStrength, 0, 0, 0);
VertexBuffer ambientShadowVertexBuffer;
VertexBufferMode vertexBufferMode = ShadowTessellator::tessellateAmbientShadow(
isCasterOpaque, casterPolygon, casterVertexCount, centroid3d,
casterBounds, localClip, maxZ, ambientShadowVertexBuffer);
drawVertexBuffer(vertexBufferMode, ambientShadowVertexBuffer, &paint);
drawVertexBuffer(*ambientShadowVertexBuffer, &paint);
}
if (mCaches.propertySpotShadowStrength > 0) {
if (spotShadowVertexBuffer && mCaches.propertySpotShadowStrength > 0) {
paint.setARGB(casterAlpha * mCaches.propertySpotShadowStrength, 0, 0, 0);
VertexBuffer spotShadowVertexBuffer;
VertexBufferMode vertexBufferMode = ShadowTessellator::tessellateSpotShadow(
isCasterOpaque, casterPolygon, casterVertexCount,
*currentTransform(), mLightCenter, mLightRadius, casterBounds, localClip,
spotShadowVertexBuffer);
drawVertexBuffer(vertexBufferMode, spotShadowVertexBuffer, &paint);
drawVertexBuffer(*spotShadowVertexBuffer, &paint);
}
return DrawGlInfo::kStatusDrew;

View File

@@ -111,12 +111,6 @@ enum ModelViewMode {
kModelViewMode_TranslateAndScale = 1,
};
enum VertexBufferMode {
kVertexBufferMode_Standard = 0,
kVertexBufferMode_OnePolyRingShadow = 1,
kVertexBufferMode_TwoPolyRingShadow = 2
};
///////////////////////////////////////////////////////////////////////////////
// Renderer
///////////////////////////////////////////////////////////////////////////////
@@ -213,8 +207,8 @@ public:
DrawOpMode drawOpMode = kDrawOpMode_Immediate);
virtual status_t drawRects(const float* rects, int count, const SkPaint* paint);
status_t drawShadow(const mat4& casterTransformXY, const mat4& casterTransformZ,
float casterAlpha, bool casterUnclipped, const SkPath* casterPerimeter);
status_t drawShadow(float casterAlpha,
const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer);
virtual void resetPaintFilter();
virtual void setupPaintFilter(int clearBits, int setBits);
@@ -348,6 +342,9 @@ public:
}
#endif
const Vector3& getLightCenter() const { return mLightCenter; }
float getLightRadius() const { return mLightRadius; }
protected:
/**
* Perform the setup specific to a frame. This method does not
@@ -661,9 +658,17 @@ private:
* @param paint The paint to render with
* @param useOffset Offset the vertexBuffer (used in drawing non-AA lines)
*/
status_t drawVertexBuffer(VertexBufferMode mode, const VertexBuffer& vertexBuffer,
status_t drawVertexBuffer(float translateX, float translateY, const VertexBuffer& vertexBuffer,
const SkPaint* paint, bool useOffset = false);
/**
* Convenience for translating method
*/
status_t drawVertexBuffer(const VertexBuffer& vertexBuffer,
const SkPaint* paint, bool useOffset = false) {
return drawVertexBuffer(0.0f, 0.0f, vertexBuffer, paint, useOffset);
}
/**
* Renders the convex hull defined by the specified path as a strip of polygons.
*

View File

@@ -74,10 +74,6 @@ hash_t PathDescription::hash() const {
return JenkinsHashWhiten(hash);
}
int PathDescription::compare(const PathDescription& rhs) const {
return memcmp(this, &rhs, sizeof(PathDescription));
}
///////////////////////////////////////////////////////////////////////////////
// Utilities
///////////////////////////////////////////////////////////////////////////////
@@ -163,14 +159,7 @@ PathCache::PathCache():
} else {
INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE);
}
init();
}
PathCache::~PathCache() {
mCache.clear();
}
void PathCache::init() {
mCache.setOnEntryRemovedListener(this);
GLint maxTextureSize;
@@ -180,6 +169,10 @@ void PathCache::init() {
mDebugEnabled = readDebugLevel() & kDebugCaches;
}
PathCache::~PathCache() {
mCache.clear();
}
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
@@ -341,7 +334,7 @@ PathCache::PathProcessor::PathProcessor(Caches& caches):
}
void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
sp<PathTask> t = static_cast<PathTask* >(task.get());
PathTask* t = static_cast<PathTask*>(task.get());
ATRACE_NAME("pathPrecache");
float left, top, offset;

View File

@@ -26,6 +26,7 @@
#include "Debug.h"
#include "Properties.h"
#include "Texture.h"
#include "utils/Macros.h"
#include "utils/Pair.h"
class SkBitmap;
@@ -107,6 +108,7 @@ enum ShapeType {
};
struct PathDescription {
DESCRIPTION_TYPE(PathDescription);
ShapeType type;
SkPaint::Join join;
SkPaint::Cap cap;
@@ -148,29 +150,6 @@ struct PathDescription {
PathDescription(ShapeType shapeType, const SkPaint* paint);
hash_t hash() const;
int compare(const PathDescription& rhs) const;
bool operator==(const PathDescription& other) const {
return compare(other) == 0;
}
bool operator!=(const PathDescription& other) const {
return compare(other) != 0;
}
friend inline int strictly_order_type(
const PathDescription& lhs, const PathDescription& rhs) {
return lhs.compare(rhs) < 0;
}
friend inline int compare_type(const PathDescription& lhs, const PathDescription& rhs) {
return lhs.compare(rhs);
}
friend inline hash_t hash_type(const PathDescription& entry) {
return entry.hash();
}
};
/**

View File

@@ -57,15 +57,17 @@ namespace uirenderer {
#define ROUND_CAP_THRESH 0.25f
#define PI 3.1415926535897932f
/**
* Note: this function doesn't account for the AA case with sub-pixel line thickness (not just 0 <
* width < 1.0, canvas scale factors in as well) so this can't be used for points/lines
*/
void PathTessellator::expandBoundsForStroke(SkRect& bounds, const SkPaint* paint) {
if (paint->getStyle() != SkPaint::kFill_Style) {
float outset = paint->getStrokeWidth() * 0.5f;
if (outset == 0) outset = 0.5f; // account for hairline
bounds.outset(outset, outset);
void PathTessellator::extractTessellationScales(const Matrix4& transform,
float* scaleX, float* scaleY) {
*scaleX = 1.0f;
*scaleY = 1.0f;
if (CC_UNLIKELY(!transform.isPureTranslate())) {
float m00 = transform.data[Matrix4::kScaleX];
float m01 = transform.data[Matrix4::kSkewY];
float m10 = transform.data[Matrix4::kSkewX];
float m11 = transform.data[Matrix4::kScaleY];
*scaleX = sqrt(m00 * m00 + m01 * m01);
*scaleY = sqrt(m10 * m10 + m11 * m11);
}
}
@@ -94,18 +96,15 @@ public:
halfStrokeWidth(paint->getStrokeWidth() * 0.5f), maxAlpha(1.0f) {
// compute inverse scales
if (CC_UNLIKELY(!transform.isPureTranslate())) {
float m00 = transform.data[Matrix4::kScaleX];
float m01 = transform.data[Matrix4::kSkewY];
float m10 = transform.data[Matrix4::kSkewX];
float m11 = transform.data[Matrix4::kScaleY];
float scaleX = sqrt(m00 * m00 + m01 * m01);
float scaleY = sqrt(m10 * m10 + m11 * m11);
float scaleX, scaleY;
PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
}
if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
2 * halfStrokeWidth < inverseScaleX) {
// AA, with non-hairline stroke, width < 1 pixel. Scale alpha and treat as hairline.
maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
halfStrokeWidth = 0.0f;
}
@@ -159,10 +158,10 @@ public:
* Outset the bounds of point data (for line endpoints or points) to account for AA stroke
* geometry.
*/
void expandBoundsForStrokeAA(SkRect& bounds) const {
void expandBoundsForStroke(Rect* bounds) const {
float outset = halfStrokeWidth;
if (outset == 0) outset = 0.5f;
bounds.outset(outset * inverseScaleX + Vertex::GeometryFudgeFactor(),
bounds->outset(outset * inverseScaleX + Vertex::GeometryFudgeFactor(),
outset * inverseScaleY + Vertex::GeometryFudgeFactor());
}
};
@@ -778,21 +777,25 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
getFillVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
}
}
Rect bounds(path.getBounds());
paintInfo.expandBoundsForStroke(&bounds);
vertexBuffer.setBounds(bounds);
}
static void expandRectToCoverVertex(SkRect& rect, float x, float y) {
rect.fLeft = fminf(rect.fLeft, x);
rect.fTop = fminf(rect.fTop, y);
rect.fRight = fmaxf(rect.fRight, x);
rect.fBottom = fmaxf(rect.fBottom, y);
static void expandRectToCoverVertex(Rect& rect, float x, float y) {
rect.left = fminf(rect.left, x);
rect.top = fminf(rect.top, y);
rect.right = fmaxf(rect.right, x);
rect.bottom = fmaxf(rect.bottom, y);
}
static void expandRectToCoverVertex(SkRect& rect, const Vertex& vertex) {
static void expandRectToCoverVertex(Rect& rect, const Vertex& vertex) {
expandRectToCoverVertex(rect, vertex.x, vertex.y);
}
template <class TYPE>
static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer,
const float* points, int count, SkRect& bounds) {
const float* points, int count, Rect& bounds) {
bounds.set(points[0], points[1], points[0], points[1]);
int numPoints = count / 2;
@@ -807,7 +810,7 @@ static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer,
}
void PathTessellator::tessellatePoints(const float* points, int count, const SkPaint* paint,
const mat4& transform, SkRect& bounds, VertexBuffer& vertexBuffer) {
const mat4& transform, VertexBuffer& vertexBuffer) {
const PaintInfo paintInfo(paint, transform);
// determine point shape
@@ -830,6 +833,7 @@ void PathTessellator::tessellatePoints(const float* points, int count, const SkP
if (!outlineVertices.size()) return;
Rect bounds;
// tessellate, then duplicate outline across points
int numPoints = count / 2;
VertexBuffer tempBuffer;
@@ -843,12 +847,12 @@ void PathTessellator::tessellatePoints(const float* points, int count, const SkP
}
// expand bounds from vertex coords to pixel data
paintInfo.expandBoundsForStrokeAA(bounds);
paintInfo.expandBoundsForStroke(&bounds);
vertexBuffer.setBounds(bounds);
}
void PathTessellator::tessellateLines(const float* points, int count, const SkPaint* paint,
const mat4& transform, SkRect& bounds, VertexBuffer& vertexBuffer) {
const mat4& transform, VertexBuffer& vertexBuffer) {
ATRACE_CALL();
const PaintInfo paintInfo(paint, transform);
@@ -868,6 +872,7 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa
tempVertices.push();
tempVertices.push();
Vertex* tempVerticesData = tempVertices.editArray();
Rect bounds;
bounds.set(points[0], points[1], points[0], points[1]);
for (int i = 0; i < count; i += 4) {
Vertex::set(&(tempVerticesData[0]), points[i + 0], points[i + 1]);
@@ -892,7 +897,8 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa
}
// expand bounds from vertex coords to pixel data
paintInfo.expandBoundsForStrokeAA(bounds);
paintInfo.expandBoundsForStroke(&bounds);
vertexBuffer.setBounds(bounds);
}
///////////////////////////////////////////////////////////////////////////////

View File

@@ -29,7 +29,15 @@ namespace uirenderer {
class PathTessellator {
public:
static void expandBoundsForStroke(SkRect& bounds, const SkPaint* paint);
/**
* Populates scaleX and scaleY with the 'tessellation scale' of the transform - the effective X
* and Y scales that tessellation will take into account when generating the 1.0 pixel thick
* ramp.
*
* Two instances of the same shape (size, paint, etc.) will only generate the same vertices if
* their tessellation scales are equal.
*/
static void extractTessellationScales(const Matrix4& transform, float* scaleX, float* scaleY);
/**
* Populates a VertexBuffer with a tessellated approximation of the input convex path, as a single
@@ -54,11 +62,10 @@ public:
* @param paint The paint the points will be drawn with indicating AA, stroke width & cap
* @param transform The transform the points will be drawn with, used to drive stretch-aware path
* vertex approximation, and correct AA ramp offsetting
* @param bounds An output rectangle, which returns the total area covered by the output buffer
* @param vertexBuffer The output buffer
*/
static void tessellatePoints(const float* points, int count, const SkPaint* paint,
const mat4& transform, SkRect& bounds, VertexBuffer& vertexBuffer);
const mat4& transform, VertexBuffer& vertexBuffer);
/**
* Populates a VertexBuffer with a tessellated approximation of lines as a single triangle
@@ -69,11 +76,10 @@ public:
* @param paint The paint the lines will be drawn with indicating AA, stroke width & cap
* @param transform The transform the points will be drawn with, used to drive stretch-aware path
* vertex approximation, and correct AA ramp offsetting
* @param bounds An output rectangle, which returns the total area covered by the output buffer
* @param vertexBuffer The output buffer
*/
static void tessellateLines(const float* points, int count, const SkPaint* paint,
const mat4& transform, SkRect& bounds, VertexBuffer& vertexBuffer);
const mat4& transform, VertexBuffer& vertexBuffer);
/**
* Approximates a convex, CW outline into a Vector of 2d vertices.

View File

@@ -176,6 +176,7 @@ enum DebugLevel {
#define PROPERTY_RENDER_BUFFER_CACHE_SIZE "ro.hwui.r_buffer_cache_size"
#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
#define PROPERTY_VERTEX_CACHE_SIZE "ro.hwui.vertex_cache_size"
#define PROPERTY_PATCH_CACHE_SIZE "ro.hwui.patch_cache_size"
#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
#define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
@@ -222,6 +223,7 @@ enum DebugLevel {
#define DEFAULT_LAYER_CACHE_SIZE 16.0f
#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
#define DEFAULT_PATH_CACHE_SIZE 10.0f
#define DEFAULT_VERTEX_CACHE_SIZE 1.0f
#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f

View File

@@ -186,6 +186,13 @@ public:
bottom += delta;
}
void outset(float xdelta, float ydelta) {
left -= xdelta;
top -= ydelta;
right += xdelta;
bottom += ydelta;
}
/**
* Similar to snapToPixelBoundaries, but estimates bounds conservatively to handle GL rounding
* errors.

View File

@@ -34,7 +34,7 @@ static inline T max(T a, T b) {
return a > b ? a : b;
}
VertexBufferMode ShadowTessellator::tessellateAmbientShadow(bool isCasterOpaque,
void ShadowTessellator::tessellateAmbientShadow(bool isCasterOpaque,
const Vector3* casterPolygon, int casterVertexCount,
const Vector3& centroid3d, const Rect& casterBounds,
const Rect& localClip, float maxZ, VertexBuffer& shadowVertexBuffer) {
@@ -57,16 +57,15 @@ VertexBufferMode ShadowTessellator::tessellateAmbientShadow(bool isCasterOpaque,
#if DEBUG_SHADOW
ALOGD("Ambient shadow is out of clip rect!");
#endif
return kVertexBufferMode_OnePolyRingShadow;
return;
}
return AmbientShadow::createAmbientShadow(isCasterOpaque, casterPolygon,
AmbientShadow::createAmbientShadow(isCasterOpaque, casterPolygon,
casterVertexCount, centroid3d, heightFactor, geomFactor,
shadowVertexBuffer);
}
VertexBufferMode ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque,
void ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque,
const Vector3* casterPolygon, int casterVertexCount,
const mat4& receiverTransform, const Vector3& lightCenter, int lightRadius,
const Rect& casterBounds, const Rect& localClip, VertexBuffer& shadowVertexBuffer) {
@@ -107,19 +106,17 @@ VertexBufferMode ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque,
#if DEBUG_SHADOW
ALOGD("Spot shadow is out of clip rect!");
#endif
return kVertexBufferMode_OnePolyRingShadow;
return;
}
VertexBufferMode mode = SpotShadow::createSpotShadow(isCasterOpaque,
SpotShadow::createSpotShadow(isCasterOpaque,
casterPolygon, casterVertexCount, adjustedLightCenter, lightRadius,
lightVertexCount, shadowVertexBuffer);
#if DEBUG_SHADOW
if(shadowVertexBuffer.getVertexCount() <= 0) {
ALOGD("Spot shadow generation failed %d", shadowVertexBuffer.getVertexCount());
}
#endif
return mode;
}
void ShadowTessellator::generateShadowIndices(uint16_t* shadowIndices) {

View File

@@ -66,12 +66,12 @@ namespace uirenderer {
class ShadowTessellator {
public:
static VertexBufferMode tessellateAmbientShadow(bool isCasterOpaque,
static void tessellateAmbientShadow(bool isCasterOpaque,
const Vector3* casterPolygon, int casterVertexCount,
const Vector3& centroid3d, const Rect& casterBounds,
const Rect& localClip, float maxZ, VertexBuffer& shadowVertexBuffer);
static VertexBufferMode tessellateSpotShadow(bool isCasterOpaque,
static void tessellateSpotShadow(bool isCasterOpaque,
const Vector3* casterPolygon, int casterVertexCount,
const mat4& receiverTransform, const Vector3& lightCenter, int lightRadius,
const Rect& casterBounds, const Rect& localClip, VertexBuffer& shadowVertexBuffer);

View File

@@ -500,14 +500,14 @@ void SpotShadow::computeLightPolygon(int points, const Vector3& lightCenter,
* empty strip if error.
*
*/
VertexBufferMode SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3* poly,
void SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3* poly,
int polyLength, const Vector3& lightCenter, float lightSize,
int lightVertexCount, VertexBuffer& retStrips) {
Vector3 light[lightVertexCount * 3];
computeLightPolygon(lightVertexCount, lightCenter, lightSize, light);
computeSpotShadow(isCasterOpaque, light, lightVertexCount, lightCenter, poly,
polyLength, retStrips);
return kVertexBufferMode_TwoPolyRingShadow;
retStrips.setMode(VertexBuffer::kTwoPolyRingShadow);
}
/**

View File

@@ -26,7 +26,7 @@ namespace uirenderer {
class SpotShadow {
public:
static VertexBufferMode createSpotShadow(bool isCasterOpaque, const Vector3* poly,
static void createSpotShadow(bool isCasterOpaque, const Vector3* poly,
int polyLength, const Vector3& lightCenter, float lightSize,
int lightVertexCount, VertexBuffer& retStrips);

View File

@@ -101,6 +101,10 @@ void StatefulBaseRenderer::restoreToCount(int saveCount) {
// Matrix
///////////////////////////////////////////////////////////////////////////////
void StatefulBaseRenderer::getMatrix(Matrix4* matrix) const {
matrix->load(*(mSnapshot->transform));
}
void StatefulBaseRenderer::getMatrix(SkMatrix* matrix) const {
mSnapshot->transform->copyTo(*matrix);
}

View File

@@ -69,6 +69,7 @@ public:
// int alpha, SkXfermode::Mode mode, int flags);
// Matrix
void getMatrix(Matrix4* outMatrix) const;
virtual void getMatrix(SkMatrix* outMatrix) const;
virtual void translate(float dx, float dy, float dz = 0.0f);
virtual void rotate(float degrees);

View File

@@ -0,0 +1,476 @@
/*
* 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.
*/
#define LOG_TAG "OpenGLRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#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)
, cap(SkPaint::kDefault_Cap)
, style(SkPaint::kFill_Style)
, strokeWidth(1.0f) {
memset(&shape, 0, sizeof(Shape));
}
TessellationCache::Description::Description(Type type)
: type(type)
, cap(SkPaint::kDefault_Cap)
, style(SkPaint::kFill_Style)
, strokeWidth(1.0f) {
memset(&shape, 0, sizeof(Shape));
}
TessellationCache::Description::Description(Type type, const SkPaint* paint)
: type(type)
, cap(paint->getStrokeCap())
, style(paint->getStyle())
, strokeWidth(paint->getStrokeWidth()) {
memset(&shape, 0, sizeof(Shape));
}
hash_t TessellationCache::Description::hash() const {
uint32_t hash = JenkinsHashMix(0, type);
hash = JenkinsHashMix(hash, cap);
hash = JenkinsHashMix(hash, style);
hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
return JenkinsHashWhiten(hash);
}
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,
const SkPaint* paint)
: tessellator(tessellator)
, description(description)
, paint(*paint) {
}
~TessellationTask() {}
Tessellator tessellator;
Description description;
//copied, since input paint may not be immutable
const SkPaint paint;
};
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->paint);
t->setResult(buffer);
}
};
struct 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 - only the localClip is deep copied, since other pointers point at Allocator controlled
// objects, which are safe for the entire frame
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,
*drawTransform, lightCenter, lightRadius, casterBounds, *localClip,
spotBuffer);
// TODO: set ambientBuffer & spotBuffer's bounds for correct layer damage
}
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
///////////////////////////////////////////////////////////////////////////////
static VertexBuffer* tessellatePath(const SkPath& path, const SkPaint* paint,
float scaleX, float scaleY) {
VertexBuffer* buffer = new VertexBuffer();
Matrix4 matrix;
matrix.loadScale(scaleX, scaleY, 1);
PathTessellator::tessellatePath(path, paint, matrix, *buffer);
return buffer;
}
TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(
const Description& entry, Tessellator tessellator, const SkPaint* paint) {
Buffer* buffer = mCache.get(entry);
if (!buffer) {
// not cached, enqueue a task to fill the buffer
sp<TessellationTask> task = new TessellationTask(tessellator, entry, paint);
buffer = new Buffer(task);
if (mProcessor == NULL) {
mProcessor = new TessellationProcessor(Caches::getInstance());
}
mProcessor->add(task);
mCache.put(entry, buffer);
}
return buffer;
}
///////////////////////////////////////////////////////////////////////////////
// Rounded rects
///////////////////////////////////////////////////////////////////////////////
static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description,
const SkPaint& paint) {
SkRect rect = SkRect::MakeWH(description.shape.roundRect.mWidth,
description.shape.roundRect.mHeight);
float rx = description.shape.roundRect.mRx;
float ry = description.shape.roundRect.mRy;
if (paint.getStyle() == SkPaint::kStrokeAndFill_Style) {
float outset = paint.getStrokeWidth() / 2;
rect.outset(outset, outset);
rx += outset;
ry += outset;
}
SkPath path;
path.addRoundRect(rect, rx, ry);
return tessellatePath(path, &paint,
description.shape.roundRect.mScaleX, description.shape.roundRect.mScaleY);
}
TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(const Matrix4& transform,
float width, float height, float rx, float ry, const SkPaint* paint) {
Description entry(Description::kRoundRect, paint);
entry.shape.roundRect.mWidth = width;
entry.shape.roundRect.mHeight = height;
entry.shape.roundRect.mRx = rx;
entry.shape.roundRect.mRy = ry;
PathTessellator::extractTessellationScales(transform,
&entry.shape.roundRect.mScaleX, &entry.shape.roundRect.mScaleY);
return getOrCreateBuffer(entry, &tessellateRoundRect, paint);
}
const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform,
float width, float height, float rx, float ry, const SkPaint* paint) {
return getRoundRectBuffer(transform, width, height, rx, ry, paint)->getVertexBuffer();
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -0,0 +1,193 @@
/*
* Copyright (C) 2013 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.
*/
#ifndef ANDROID_HWUI_TESSELLATION_CACHE_H
#define ANDROID_HWUI_TESSELLATION_CACHE_H
#include <utils/LruCache.h>
#include <utils/Mutex.h>
#include <utils/Vector.h>
#include "Debug.h"
#include "utils/Macros.h"
#include "utils/Pair.h"
#include "VertexBuffer.h"
class SkBitmap;
class SkCanvas;
class SkPaint;
class SkPath;
struct SkRect;
namespace android {
namespace uirenderer {
class Caches;
///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
class TessellationCache {
public:
typedef Pair<VertexBuffer*, VertexBuffer*> vertexBuffer_pair_t;
struct Description {
DESCRIPTION_TYPE(Description);
enum Type {
kNone,
kRoundRect,
kAmbientShadow,
kSpotShadow
};
Type type;
SkPaint::Cap cap;
SkPaint::Style style;
float strokeWidth;
union Shape {
struct RoundRect {
float mScaleX;
float mScaleY;
float mWidth;
float mHeight;
float mRx;
float mRy;
} roundRect;
} shape;
Description();
Description(Type type);
Description(Type type, const SkPaint* paint);
hash_t hash() const;
};
struct ShadowDescription {
DESCRIPTION_TYPE(ShadowDescription);
const void* nodeKey;
float matrixData[16];
ShadowDescription();
ShadowDescription(const void* nodeKey, const Matrix4* drawTransform);
hash_t hash() const;
};
TessellationCache();
~TessellationCache();
/**
* Clears the cache. This causes all TessellationBuffers to be deleted.
*/
void clear();
/**
* Sets the maximum size of the cache in bytes.
*/
void setMaxSize(uint32_t maxSize);
/**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
/**
* Returns the current size of the cache in bytes.
*/
uint32_t getSize();
/**
* Trims the contents of the cache, removing items until it's under its
* specified limit.
*
* Trimming is used for caches that support pre-caching from a worker
* thread. During pre-caching the maximum limit of the cache can be
* exceeded for the duration of the frame. It is therefore required to
* trim the cache at the end of the frame to keep the total amount of
* memory used under control.
*
* Also removes transient Shadow VertexBuffers, which aren't cached between frames.
*/
void trim();
// TODO: precache/get for Oval, Lines, Points, etc.
void precacheRoundRect(const Matrix4& transform,
float width, float height, float rx, float ry, const SkPaint* paint) {
getRoundRectBuffer(transform, width, height, rx, ry, paint);
}
const VertexBuffer* getRoundRect(const Matrix4& transform,
float width, float height, float rx, float ry, const SkPaint* paint);
void precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
bool opaque, const SkPath* casterPerimeter,
const Matrix4* transformXY, const Matrix4* transformZ,
const Vector3& lightCenter, float lightRadius);
void 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);
private:
class Buffer;
class TessellationTask;
class TessellationProcessor;
typedef VertexBuffer* (*Tessellator)(const Description&, const SkPaint&);
Buffer* getRoundRectBuffer(const Matrix4& transform,
float width, float height, float rx, float ry, const SkPaint* paint);
Buffer* getOrCreateBuffer(const Description& entry,
Tessellator tessellator, const SkPaint* paint);
uint32_t mSize;
uint32_t mMaxSize;
bool mDebugEnabled;
mutable Mutex mLock;
///////////////////////////////////////////////////////////////////////////////
// General tessellation caching
///////////////////////////////////////////////////////////////////////////////
sp<TaskProcessor<VertexBuffer*> > mProcessor;
LruCache<Description, Buffer*> mCache;
class BufferRemovedListener : public OnEntryRemoved<Description, Buffer*> {
void operator()(Description& description, Buffer*& buffer);
};
BufferRemovedListener mBufferRemovedListener;
///////////////////////////////////////////////////////////////////////////////
// Shadow tessellation caching
///////////////////////////////////////////////////////////////////////////////
sp<TaskProcessor<vertexBuffer_pair_t*> > mShadowProcessor;
// holds a pointer, and implicit strong ref to each shadow task of the frame
LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*> mShadowCache;
class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t*>*> {
void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t*>*& bufferPairTask) {
bufferPairTask->decStrong(NULL);
}
};
BufferPairRemovedListener mBufferPairRemovedListener;
}; // class TessellationCache
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_PATH_CACHE_H

View File

@@ -23,10 +23,18 @@ namespace uirenderer {
class VertexBuffer {
public:
VertexBuffer():
mBuffer(0),
mVertexCount(0),
mCleanupMethod(NULL)
enum Mode {
kStandard = 0,
kOnePolyRingShadow = 1,
kTwoPolyRingShadow = 2
};
VertexBuffer()
: mBuffer(0)
, mVertexCount(0)
, mByteCount(0)
, mMode(kStandard)
, mCleanupMethod(NULL)
{}
~VertexBuffer() {
@@ -37,7 +45,7 @@ public:
This should be the only method used by the Tessellator. Subsequent calls to
alloc will allocate space within the first allocation (useful if you want to
eventually allocate multiple regions within a single VertexBuffer, such as
with PathTessellator::tesselateLines())
with PathTessellator::tessellateLines())
*/
template <class TYPE>
TYPE* alloc(int vertexCount) {
@@ -52,6 +60,7 @@ public:
return reallocBuffer;
}
mVertexCount = vertexCount;
mByteCount = mVertexCount * sizeof(TYPE);
mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount];
mCleanupMethod = &(cleanup<TYPE>);
@@ -71,7 +80,13 @@ public:
}
const void* getBuffer() const { return mBuffer; }
const Rect& getBounds() const { return mBounds; }
unsigned int getVertexCount() const { return mVertexCount; }
unsigned int getSize() const { return mByteCount; }
Mode getMode() const { return mMode; }
void setBounds(Rect bounds) { mBounds = bounds; }
void setMode(Mode mode) { mMode = mode; }
template <class TYPE>
void createDegenerateSeparators(int allocSize) {
@@ -88,8 +103,11 @@ private:
delete[] (TYPE*)buffer;
}
Rect mBounds;
void* mBuffer;
unsigned int mVertexCount;
unsigned int mByteCount;
Mode mMode;
void* mReallocBuffer; // used for multi-allocation

View File

@@ -16,9 +16,10 @@
#include <sys/sysinfo.h>
#include "TaskManager.h"
#include "Task.h"
#include "TaskProcessor.h"
#include "TaskManager.h"
#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
@@ -31,7 +32,8 @@ TaskManager::TaskManager() {
// Get the number of available CPUs. This value does not change over time.
int cpuCount = sysconf(_SC_NPROCESSORS_CONF);
for (int i = 0; i < cpuCount / 2; i++) {
int workerCount = MathUtils::max(1, cpuCount / 2);
for (int i = 0; i < workerCount; i++) {
String8 name;
name.appendFormat("hwuiTask%d", i + 1);
mThreads.add(new WorkerThread(name));

View File

@@ -21,5 +21,12 @@
Type(const Type&); \
void operator=(const Type&)
#define DESCRIPTION_TYPE(Type) \
int compare(const Type& rhs) const { return memcmp(this, &rhs, sizeof(Type));} \
bool operator==(const Type& other) const { return compare(other) == 0; } \
bool operator!=(const Type& other) const { return compare(other) != 0; } \
friend inline int strictly_order_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs) < 0; } \
friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \
friend inline hash_t hash_type(const Type& entry) { return entry.hash(); }
#endif /* MACROS_H */

View File

@@ -38,6 +38,10 @@ public:
return isZero(valueA - valueB);
}
inline static int max(int a, int b) {
return a > b ? a : b;
}
inline static int min(int a, int b) {
return a < b ? a : b;
}