Clipping performance improvements

Create a ClipArea class to handle tracking clip regions. This class can
select the most efficient implementation depending on the types of
clipping presented.

ClipArea re-used the rectangle and region-based clipping
implementations as well as adding a "list of rotated rectangles"
approach that is more efficient for rotated views with children.

Change-Id: I2133761a2462ebc0852b394220e265974b3086f0
This commit is contained in:
Rob Tsuk
2015-01-06 13:22:54 -08:00
parent 382d6a9ed2
commit 487a92caef
15 changed files with 884 additions and 204 deletions

View File

@@ -18,6 +18,7 @@ LOCAL_SRC_FILES := \
AssetAtlas.cpp \
Caches.cpp \
CanvasState.cpp \
ClipArea.cpp \
DamageAccumulator.cpp \
DisplayList.cpp \
DeferredDisplayList.cpp \

View File

@@ -155,47 +155,18 @@ void CanvasState::concatMatrix(const Matrix4& matrix) {
///////////////////////////////////////////////////////////////////////////////
bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
if (CC_LIKELY(currentTransform()->rectToRect())) {
mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
return !mSnapshot->clipRect->isEmpty();
}
SkPath path;
path.addRect(left, top, right, bottom);
return CanvasState::clipPath(&path, op);
mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
return !mSnapshot->clipIsEmpty();
}
bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) {
SkMatrix transform;
currentTransform()->copyTo(transform);
SkPath transformed;
path->transform(transform, &transformed);
SkRegion clip;
if (!mSnapshot->previous->clipRegion->isEmpty()) {
clip.setRegion(*mSnapshot->previous->clipRegion);
} else {
if (mSnapshot->previous == firstSnapshot()) {
clip.setRect(0, 0, getWidth(), getHeight());
} else {
Rect* bounds = mSnapshot->previous->clipRect;
clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
}
}
SkRegion region;
region.setPath(transformed, clip);
// region is the transformed input path, masked by the previous clip
mDirtyClip |= mSnapshot->clipRegionTransformed(region, op);
return !mSnapshot->clipRect->isEmpty();
mDirtyClip |= mSnapshot->clipPath(*path, op);
return !mSnapshot->clipIsEmpty();
}
bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) {
mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op);
return !mSnapshot->clipRect->isEmpty();
return !mSnapshot->clipIsEmpty();
}
void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
@@ -245,7 +216,7 @@ bool CanvasState::calculateQuickRejectForScissor(float left, float top,
currentTransform()->mapRect(r);
r.snapGeometryToPixelBoundaries(snapOut);
Rect clipRect(*currentClipRect());
Rect clipRect(currentClipRect());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
@@ -273,7 +244,7 @@ bool CanvasState::quickRejectConservative(float left, float top,
currentTransform()->mapRect(r);
r.roundOut(); // rounded out to be conservative
Rect clipRect(*currentClipRect());
Rect clipRect(currentClipRect());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;

View File

@@ -120,10 +120,6 @@ public:
bool clipPath(const SkPath* path, SkRegion::Op op);
bool clipRegion(const SkRegion* region, SkRegion::Op op);
bool isCurrentClipSimple() const {
return currentSnapshot()->clipRegion->isEmpty();
}
/**
* Sets a "clipping outline", which is independent from the regular clip.
* Currently only supports rectangles or rounded rectangles; passing in a
@@ -150,7 +146,7 @@ public:
void setInvisible(bool value) { mSnapshot->invisible = value; }
inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
inline const Rect* currentClipRect() const { return currentSnapshot()->clipRect; }
inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
inline Region* currentRegion() const { return currentSnapshot()->region; }
inline int currentFlags() const { return currentSnapshot()->flags; }
const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }

368
libs/hwui/ClipArea.cpp Normal file
View File

@@ -0,0 +1,368 @@
/*
* Copyright (C) 2015 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 "ClipArea.h"
#include <SkPath.h>
#include <limits>
#include "Rect.h"
namespace android {
namespace uirenderer {
static bool intersect(Rect& r, const Rect& r2) {
bool hasIntersection = r.intersect(r2);
if (!hasIntersection) {
r.setEmpty();
}
return hasIntersection;
}
static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
Vertex v;
v.x = x;
v.y = y;
transform.mapPoint(v.x, v.y);
transformedBounds.expandToCoverVertex(v.x, v.y);
}
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
const float kMinFloat = std::numeric_limits<float>::lowest();
const float kMaxFloat = std::numeric_limits<float>::max();
Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat };
handlePoint(transformedBounds, transform, r.left, r.top);
handlePoint(transformedBounds, transform, r.right, r.top);
handlePoint(transformedBounds, transform, r.left, r.bottom);
handlePoint(transformedBounds, transform, r.right, r.bottom);
return transformedBounds;
}
/*
* TransformedRectangle
*/
TransformedRectangle::TransformedRectangle() {
}
TransformedRectangle::TransformedRectangle(const Rect& bounds,
const Matrix4& transform)
: mBounds(bounds)
, mTransform(transform) {
}
bool TransformedRectangle::canSimplyIntersectWith(
const TransformedRectangle& other) const {
return mTransform == other.mTransform;
}
bool TransformedRectangle::intersectWith(const TransformedRectangle& other) {
Rect translatedBounds(other.mBounds);
return intersect(mBounds, translatedBounds);
}
bool TransformedRectangle::isEmpty() const {
return mBounds.isEmpty();
}
/*
* RectangleList
*/
RectangleList::RectangleList()
: mTransformedRectanglesCount(0) {
}
bool RectangleList::isEmpty() const {
if (mTransformedRectanglesCount < 1) {
return true;
}
for (int i = 0; i < mTransformedRectanglesCount; i++) {
if (mTransformedRectangles[i].isEmpty()) {
return true;
}
}
return false;
}
int RectangleList::getTransformedRectanglesCount() const {
return mTransformedRectanglesCount;
}
const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const {
return mTransformedRectangles[i];
}
void RectangleList::setEmpty() {
mTransformedRectanglesCount = 0;
}
void RectangleList::set(const Rect& bounds, const Matrix4& transform) {
mTransformedRectanglesCount = 1;
mTransformedRectangles[0] = TransformedRectangle(bounds, transform);
}
bool RectangleList::intersectWith(const Rect& bounds,
const Matrix4& transform) {
TransformedRectangle newRectangle(bounds, transform);
// Try to find a rectangle with a compatible transformation
int index = 0;
for (; index < mTransformedRectanglesCount; index++) {
TransformedRectangle& tr(mTransformedRectangles[index]);
if (tr.canSimplyIntersectWith(newRectangle)) {
tr.intersectWith(newRectangle);
return true;
}
}
// Add it to the list if there is room
if (index < kMaxTransformedRectangles) {
mTransformedRectangles[index] = newRectangle;
mTransformedRectanglesCount += 1;
return true;
}
// This rectangle list is full
return false;
}
Rect RectangleList::calculateBounds() const {
Rect bounds;
for (int index = 0; index < mTransformedRectanglesCount; index++) {
const TransformedRectangle& tr(mTransformedRectangles[index]);
if (index == 0) {
bounds = tr.transformedBounds();
} else {
bounds.intersect(tr.transformedBounds());
}
}
return bounds;
}
static SkPath pathFromTransformedRectangle(const Rect& bounds,
const Matrix4& transform) {
SkPath rectPath;
SkPath rectPathTransformed;
rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom);
SkMatrix skTransform;
transform.copyTo(skTransform);
rectPath.transform(skTransform, &rectPathTransformed);
return rectPathTransformed;
}
SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
SkRegion rectangleListAsRegion;
for (int index = 0; index < mTransformedRectanglesCount; index++) {
const TransformedRectangle& tr(mTransformedRectangles[index]);
SkPath rectPathTransformed = pathFromTransformedRectangle(
tr.getBounds(), tr.getTransform());
if (index == 0) {
rectangleListAsRegion.setPath(rectPathTransformed, clip);
} else {
SkRegion rectRegion;
rectRegion.setPath(rectPathTransformed, clip);
rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op);
}
}
return rectangleListAsRegion;
}
/*
* ClipArea
*/
ClipArea::ClipArea()
: mMode(kModeRectangle) {
}
/*
* Interface
*/
void ClipArea::setViewportDimensions(int width, int height) {
mViewportBounds.set(0, 0, width, height);
mClipRect = mViewportBounds;
}
void ClipArea::setEmpty() {
mMode = kModeRectangle;
mClipRect.setEmpty();
mClipRegion.setEmpty();
mRectangleList.setEmpty();
}
void ClipArea::setClip(float left, float top, float right, float bottom) {
mMode = kModeRectangle;
mClipRect.set(left, top, right, bottom);
mClipRegion.setEmpty();
}
bool ClipArea::clipRectWithTransform(float left, float top, float right,
float bottom, const mat4* transform, SkRegion::Op op) {
Rect r(left, top, right, bottom);
return clipRectWithTransform(r, transform, op);
}
bool ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op) {
switch (mMode) {
case kModeRectangle:
return rectangleModeClipRectWithTransform(r, transform, op);
case kModeRectangleList:
return rectangleListModeClipRectWithTransform(r, transform, op);
case kModeRegion:
return regionModeClipRectWithTransform(r, transform, op);
}
return false;
}
bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
enterRegionMode();
mClipRegion.op(region, op);
setClipRectToRegionBounds();
return true;
}
bool ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op) {
SkMatrix skTransform;
transform->copyTo(skTransform);
SkPath transformed;
path.transform(skTransform, &transformed);
SkRegion region;
regionFromPath(transformed, region);
return clipRegion(region, op);
}
/*
* Rectangle mode
*/
void ClipArea::enterRectangleMode() {
// Entering rectangle mode discards any
// existing clipping information from the other modes.
// The only way this occurs is by a clip setting operation.
mMode = kModeRectangle;
}
bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
if (op != SkRegion::kIntersect_Op) {
enterRegionMode();
return regionModeClipRectWithTransform(r, transform, op);
}
if (transform->rectToRect()) {
Rect transformed(r);
transform->mapRect(transformed);
bool hasIntersection = mClipRect.intersect(transformed);
if (!hasIntersection) {
mClipRect.setEmpty();
}
return true;
}
enterRectangleListMode();
return rectangleListModeClipRectWithTransform(r, transform, op);
}
bool ClipArea::rectangleModeClipRectWithTransform(float left, float top,
float right, float bottom, const mat4* transform, SkRegion::Op op) {
Rect r(left, top, right, bottom);
bool result = rectangleModeClipRectWithTransform(r, transform, op);
mClipRect = mRectangleList.calculateBounds();
return result;
}
/*
* RectangleList mode implementation
*/
void ClipArea::enterRectangleListMode() {
// Is is only legal to enter rectangle list mode from
// rectangle mode, since rectangle list mode cannot represent
// all clip areas that can be represented by a region.
ALOG_ASSERT(mMode == kModeRectangle);
mMode = kModeRectangleList;
mRectangleList.set(mClipRect, Matrix4::identity());
}
bool ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
if (op != SkRegion::kIntersect_Op
|| !mRectangleList.intersectWith(r, *transform)) {
enterRegionMode();
return regionModeClipRectWithTransform(r, transform, op);
}
return true;
}
bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top,
float right, float bottom, const mat4* transform, SkRegion::Op op) {
Rect r(left, top, right, bottom);
return rectangleListModeClipRectWithTransform(r, transform, op);
}
/*
* Region mode implementation
*/
void ClipArea::enterRegionMode() {
if (mMode != kModeRegion) {
if (mMode == kModeRectangle) {
mClipRegion.setRect(mClipRect.left, mClipRect.top,
mClipRect.right, mClipRect.bottom);
} else {
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
setClipRectToRegionBounds();
}
mMode = kModeRegion;
}
}
bool ClipArea::regionModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
SkRegion transformedRectRegion;
regionFromPath(transformedRect, transformedRectRegion);
mClipRegion.op(transformedRectRegion, op);
setClipRectToRegionBounds();
return true;
}
bool ClipArea::regionModeClipRectWithTransform(float left, float top,
float right, float bottom, const mat4* transform, SkRegion::Op op) {
return regionModeClipRectWithTransform(Rect(left, top, right, bottom),
transform, op);
}
void ClipArea::setClipRectToRegionBounds() {
if (!mClipRegion.isEmpty()) {
mClipRect.set(mClipRegion.getBounds());
if (mClipRegion.isRect()) {
mClipRegion.setEmpty();
}
} else {
mClipRect.setEmpty();
}
}
} /* namespace uirenderer */
} /* namespace android */

175
libs/hwui/ClipArea.h Normal file
View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2015 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 CLIPAREA_H
#define CLIPAREA_H
#include <SkRegion.h>
#include "Matrix.h"
#include "Rect.h"
#include "utils/Pair.h"
namespace android {
namespace uirenderer {
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);
class TransformedRectangle {
public:
TransformedRectangle();
TransformedRectangle(const Rect& bounds, const Matrix4& transform);
bool canSimplyIntersectWith(const TransformedRectangle& other) const;
bool intersectWith(const TransformedRectangle& other);
bool isEmpty() const;
const Rect& getBounds() const {
return mBounds;
}
Rect transformedBounds() const {
Rect transformedBounds(transformAndCalculateBounds(mBounds, mTransform));
return transformedBounds;
}
const Matrix4& getTransform() const {
return mTransform;
}
private:
Rect mBounds;
Matrix4 mTransform;
};
class RectangleList {
public:
RectangleList();
bool isEmpty() const;
int getTransformedRectanglesCount() const;
const TransformedRectangle& getTransformedRectangle(int i) const;
void setEmpty();
void set(const Rect& bounds, const Matrix4& transform);
bool intersectWith(const Rect& bounds, const Matrix4& transform);
SkRegion convertToRegion(const SkRegion& clip) const;
Rect calculateBounds() const;
private:
enum {
kMaxTransformedRectangles = 5
};
int mTransformedRectanglesCount;
TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
};
class ClipArea {
public:
ClipArea();
void setViewportDimensions(int width, int height);
bool isEmpty() const {
return mClipRect.isEmpty();
}
void setEmpty();
void setClip(float left, float top, float right, float bottom);
bool clipRectWithTransform(float left, float top, float right, float bottom,
const mat4* transform, SkRegion::Op op = SkRegion::kIntersect_Op);
bool clipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op = SkRegion::kIntersect_Op);
bool clipRegion(const SkRegion& region, SkRegion::Op op = SkRegion::kIntersect_Op);
bool clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op);
const Rect& getClipRect() const {
return mClipRect;
}
const SkRegion& getClipRegion() const {
return mClipRegion;
}
const RectangleList& getRectangleList() const {
return mRectangleList;
}
bool isRegion() const {
return kModeRegion == mMode;
}
bool isSimple() const {
return mMode == kModeRectangle;
}
bool isRectangleList() const {
return mMode == kModeRectangleList;
}
private:
void enterRectangleMode();
bool rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
bool rectangleModeClipRectWithTransform(float left, float top, float right,
float bottom, const mat4* transform, SkRegion::Op op);
void enterRectangleListMode();
bool rectangleListModeClipRectWithTransform(float left, float top,
float right, float bottom, const mat4* transform, SkRegion::Op op);
bool rectangleListModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op);
void enterRegionModeFromRectangleMode();
void enterRegionModeFromRectangleListMode();
void enterRegionMode();
bool regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op);
bool regionModeClipRectWithTransform(float left, float top, float right,
float bottom, const mat4* transform, SkRegion::Op op);
void ensureClipRegion();
void setClipRectToRegionBounds();
bool clipRegionOp(float left, float top, float right, float bottom,
SkRegion::Op op);
SkRegion createViewportRegion() {
return SkRegion(mViewportBounds.toSkIRect());
}
void regionFromPath(const SkPath& path, SkRegion& pathAsRegion) {
pathAsRegion.setPath(path, createViewportRegion());
}
enum Mode {
kModeRectangle,
kModeRegion,
kModeRectangleList
};
Mode mMode;
Rect mViewportBounds;
Rect mClipRect;
SkRegion mClipRegion;
RectangleList mRectangleList;
};
} /* namespace uirenderer */
} /* namespace android */
#endif /* CLIPAREA_H_ */

141
libs/hwui/OpenGLRenderer.cpp Executable file → Normal file
View File

@@ -354,7 +354,7 @@ void OpenGLRenderer::resumeAfterLayer() {
void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
if (mState.currentlyIgnored()) return;
Rect clip(*mState.currentClipRect());
Rect clip(mState.currentClipRect());
clip.snapToPixelBoundaries();
// Since we don't know what the functor will draw, let's dirty
@@ -619,7 +619,7 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool
currentTransform()->mapRect(bounds);
// Layers only make sense if they are in the framebuffer's bounds
if (bounds.intersect(*mState.currentClipRect())) {
if (bounds.intersect(mState.currentClipRect())) {
// We cannot work with sub-pixels in this case
bounds.snapToPixelBoundaries();
@@ -1249,7 +1249,7 @@ void OpenGLRenderer::dirtyLayer(const float left, const float top,
}
void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
if (bounds.intersect(*mState.currentClipRect())) {
if (bounds.intersect(mState.currentClipRect())) {
bounds.snapToPixelBoundaries();
android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
if (!dirty.isEmpty()) {
@@ -1328,7 +1328,7 @@ void OpenGLRenderer::clearLayerRegions() {
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
const Rect* currentClip = mState.currentClipRect();
const Rect& currentClip = mState.currentClipRect();
const mat4* currentMatrix = currentTransform();
if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1340,32 +1340,32 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef
// is used, it should more closely duplicate the quickReject logic (in how it uses
// snapToPixelBoundaries)
if(!clippedBounds.intersect(*currentClip)) {
if (!clippedBounds.intersect(currentClip)) {
// quick rejected
return true;
}
state.mClipSideFlags = kClipSide_None;
if (!currentClip->contains(state.mBounds)) {
if (!currentClip.contains(state.mBounds)) {
int& flags = state.mClipSideFlags;
// op partially clipped, so record which sides are clipped for clip-aware merging
if (currentClip->left > state.mBounds.left) flags |= kClipSide_Left;
if (currentClip->top > state.mBounds.top) flags |= kClipSide_Top;
if (currentClip->right < state.mBounds.right) flags |= kClipSide_Right;
if (currentClip->bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
if (currentClip.left > state.mBounds.left) flags |= kClipSide_Left;
if (currentClip.top > state.mBounds.top) flags |= kClipSide_Top;
if (currentClip.right < state.mBounds.right) flags |= kClipSide_Right;
if (currentClip.bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
}
state.mBounds.set(clippedBounds);
} else {
// Empty bounds implies size unknown. Label op as conservatively clipped to disable
// overdraw avoidance (since we don't know what it overlaps)
state.mClipSideFlags = kClipSide_ConservativeFull;
state.mBounds.set(*currentClip);
state.mBounds.set(currentClip);
}
}
state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip);
if (state.mClipValid) {
state.mClip.set(*currentClip);
state.mClip.set(currentClip);
}
// Transform, drawModifiers, and alpha always deferred, since they are used by state operations
@@ -1414,7 +1414,7 @@ void OpenGLRenderer::setupMergedMultiDraw(const Rect* clipRect) {
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::setScissorFromClip() {
Rect clip(*mState.currentClipRect());
Rect clip(mState.currentClipRect());
clip.snapToPixelBoundaries();
if (mCaches.setScissor(clip.left, getViewportHeight() - clip.bottom,
@@ -1448,9 +1448,74 @@ void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) {
}
}
static void handlePoint(std::vector<Vertex>& rectangleVertices, const Matrix4& transform,
float x, float y) {
Vertex v;
v.x = x;
v.y = y;
transform.mapPoint(v.x, v.y);
rectangleVertices.push_back(v);
}
static void handlePointNoTransform(std::vector<Vertex>& rectangleVertices, float x, float y) {
Vertex v;
v.x = x;
v.y = y;
rectangleVertices.push_back(v);
}
void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) {
int count = rectangleList.getTransformedRectanglesCount();
std::vector<Vertex> rectangleVertices(count * 4);
Rect scissorBox = rectangleList.calculateBounds();
scissorBox.snapToPixelBoundaries();
for (int i = 0; i < count; ++i) {
const TransformedRectangle& tr(rectangleList.getTransformedRectangle(i));
const Matrix4& transform = tr.getTransform();
Rect bounds = tr.getBounds();
if (transform.rectToRect()) {
transform.mapRect(bounds);
if (!bounds.intersect(scissorBox)) {
bounds.setEmpty();
} else {
handlePointNoTransform(rectangleVertices, bounds.left, bounds.top);
handlePointNoTransform(rectangleVertices, bounds.right, bounds.top);
handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom);
handlePointNoTransform(rectangleVertices, bounds.right, bounds.bottom);
}
} else {
handlePoint(rectangleVertices, transform, bounds.left, bounds.top);
handlePoint(rectangleVertices, transform, bounds.right, bounds.top);
handlePoint(rectangleVertices, transform, bounds.left, bounds.bottom);
handlePoint(rectangleVertices, transform, bounds.right, bounds.bottom);
}
}
mCaches.setScissor(scissorBox.left, getViewportHeight() - scissorBox.bottom,
scissorBox.getWidth(), scissorBox.getHeight());
const SkPaint* paint = nullptr;
setupDraw();
setupDrawNoTexture();
setupDrawColor(0, 0xff * currentSnapshot()->alpha);
setupDrawShader(getShader(paint));
setupDrawColorFilter(getColorFilter(paint));
setupDrawBlending(paint);
setupDrawProgram();
setupDrawDirtyRegionsDisabled();
setupDrawModelView(kModelViewMode_Translate, false,
0.0f, 0.0f, 0.0f, 0.0f, true);
setupDrawColorUniforms(getShader(paint));
setupDrawShaderUniforms(getShader(paint));
setupDrawColorFilterUniforms(getColorFilter(paint));
issueIndexedQuadDraw(&rectangleVertices[0], rectangleVertices.size() / 4);
}
void OpenGLRenderer::setStencilFromClip() {
if (!mCaches.debugOverdraw) {
if (!currentSnapshot()->clipRegion->isEmpty()) {
if (!currentSnapshot()->clipIsSimple()) {
int incrementThreshold;
EVENT_LOGD("setStencilFromClip - enabling");
// NOTE: The order here is important, we must set dirtyClip to false
@@ -1459,15 +1524,25 @@ void OpenGLRenderer::setStencilFromClip() {
ensureStencilBuffer();
mCaches.stencil.enableWrite();
const ClipArea& clipArea = currentSnapshot()->getClipArea();
// Clear and update the stencil, but first make sure we restrict drawing
bool isRectangleList = clipArea.isRectangleList();
if (isRectangleList) {
incrementThreshold = clipArea.getRectangleList().getTransformedRectanglesCount();
} else {
incrementThreshold = 0;
}
mCaches.stencil.enableWrite(incrementThreshold);
// Clean and update the stencil, but first make sure we restrict drawing
// to the region's bounds
bool resetScissor = mCaches.enableScissor();
if (resetScissor) {
// The scissor was not set so we now need to update it
setScissorFromClip();
}
mCaches.stencil.clear();
// stash and disable the outline clip state, since stencil doesn't account for outline
@@ -1478,24 +1553,30 @@ void OpenGLRenderer::setStencilFromClip() {
paint.setColor(SK_ColorBLACK);
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
// NOTE: We could use the region contour path to generate a smaller mesh
// Since we are using the stencil we could use the red book path
// drawing technique. It might increase bandwidth usage though.
if (isRectangleList) {
drawRectangleList(clipArea.getRectangleList());
} else {
// NOTE: We could use the region contour path to generate a smaller mesh
// Since we are using the stencil we could use the red book path
// drawing technique. It might increase bandwidth usage though.
// The last parameter is important: we are not drawing in the color buffer
// so we don't want to dirty the current layer, if any
drawRegionRects(*(currentSnapshot()->clipRegion), paint, false);
// The last parameter is important: we are not drawing in the color buffer
// so we don't want to dirty the current layer, if any
drawRegionRects(clipArea.getClipRegion(), paint, false);
}
if (resetScissor) mCaches.disableScissor();
mSkipOutlineClip = storedSkipOutlineClip;
mCaches.stencil.enableTest();
mCaches.stencil.enableTest(incrementThreshold);
// Draw the region used to generate the stencil if the appropriate debug
// mode is enabled
if (mCaches.debugStencilClip == Caches::kStencilShowRegion) {
// TODO: Implement for rectangle list clip areas
if (mCaches.debugStencilClip == Caches::kStencilShowRegion &&
!clipArea.isRectangleList()) {
paint.setColor(0x7f0000ff);
paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
drawRegionRects(*(currentSnapshot()->clipRegion), paint);
drawRegionRects(currentSnapshot()->getClipRegion(), paint);
}
} else {
EVENT_LOGD("setStencilFromClip - disabling");
@@ -1913,7 +1994,7 @@ void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t
// Don't avoid overdraw when visualizing, since that makes it harder to
// debug where it's coming from, and when the problem occurs.
bool avoidOverdraw = !mCaches.debugOverdraw;
DeferredDisplayList deferredList(*mState.currentClipRect(), avoidOverdraw);
DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode->defer(deferStruct, 0);
@@ -2450,7 +2531,7 @@ void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
// No need to check against the clip, we fill the clip region
if (mState.currentlyIgnored()) return;
Rect clip(*mState.currentClipRect());
Rect clip(mState.currentClipRect());
clip.snapToPixelBoundaries();
SkPaint paint;
@@ -2704,13 +2785,13 @@ void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
}
fontRenderer.setTextureFiltering(linearFilter);
const Rect* clip = pureTranslate ? writableSnapshot()->clipRect : &writableSnapshot()->getLocalClip();
const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
const bool hasActiveLayer = hasLayer();
TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
positions, hasActiveLayer ? &bounds : nullptr, &functor)) {
if (hasActiveLayer) {
if (!pureTranslate) {
@@ -2864,7 +2945,7 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
fontRenderer.setTextureFiltering(linearFilter);
// TODO: Implement better clipping for scaled/rotated text
const Rect* clip = !pureTranslate ? nullptr : mState.currentClipRect();
const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;

View File

@@ -223,9 +223,6 @@ public:
return mCaches;
}
// simple rect clip
bool isCurrentClipSimple() { return mState.isCurrentClipSimple(); }
int getViewportWidth() { return mState.getViewportWidth(); }
int getViewportHeight() { return mState.getViewportHeight(); }
@@ -433,6 +430,13 @@ protected:
*/
void attachStencilBufferToLayer(Layer* layer);
/**
* Draw a rectangle list. Currently only used for the the stencil buffer so that the stencil
* will have a value of 'n' in every unclipped pixel, where 'n' is the number of rectangles
* in the list.
*/
void drawRectangleList(const RectangleList& rectangleList);
bool quickRejectSetupScissor(float left, float top, float right, float bottom,
const SkPaint* paint = nullptr);
bool quickRejectSetupScissor(const Rect& bounds, const SkPaint* paint = nullptr) {

View File

@@ -111,6 +111,10 @@ public:
set(r.left, r.top, r.right, r.bottom);
}
inline void set(const SkIRect& r) {
set(r.left(), r.top(), r.right(), r.bottom());
}
inline float getWidth() const {
return right - left;
}
@@ -248,6 +252,14 @@ public:
bottom = fmaxf(bottom, y);
}
SkRect toSkRect() const {
return SkRect::MakeLTRB(left, top, right, bottom);
}
SkIRect toSkIRect() const {
return SkIRect::MakeLTRB(left, top, right, bottom);
}
void dump(const char* label = nullptr) const {
ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
}

View File

@@ -35,11 +35,10 @@ Snapshot::Snapshot()
, invisible(false)
, empty(false)
, alpha(1.0f)
, roundRectClipState(nullptr) {
, roundRectClipState(nullptr)
, mClipArea(&mClipAreaRoot) {
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
region = nullptr;
clipRegion = &mClipRegionRoot;
}
/**
@@ -55,6 +54,7 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
, empty(false)
, alpha(s->alpha)
, roundRectClipState(s->roundRectClipState)
, mClipArea(nullptr)
, mViewportData(s->mViewportData)
, mRelativeLightCenter(s->mRelativeLightCenter) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
@@ -65,15 +65,10 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
}
if (saveFlags & SkCanvas::kClip_SaveFlag) {
mClipRectRoot.set(*s->clipRect);
clipRect = &mClipRectRoot;
if (!s->clipRegion->isEmpty()) {
mClipRegionRoot.op(*s->clipRegion, SkRegion::kUnion_Op);
}
clipRegion = &mClipRegionRoot;
mClipAreaRoot = s->getClipArea();
mClipArea = &mClipAreaRoot;
} else {
clipRect = s->clipRect;
clipRegion = s->clipRegion;
mClipArea = s->mClipArea;
}
if (s->flags & Snapshot::kFlagFboTarget) {
@@ -88,88 +83,23 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
// Clipping
///////////////////////////////////////////////////////////////////////////////
void Snapshot::ensureClipRegion() {
if (clipRegion->isEmpty()) {
clipRegion->setRect(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
}
}
void Snapshot::copyClipRectFromRegion() {
if (!clipRegion->isEmpty()) {
const SkIRect& bounds = clipRegion->getBounds();
clipRect->set(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
if (clipRegion->isRect()) {
clipRegion->setEmpty();
}
} else {
clipRect->setEmpty();
}
}
bool Snapshot::clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op) {
SkIRect tmp;
tmp.set(left, top, right, bottom);
clipRegion->op(tmp, op);
copyClipRectFromRegion();
return true;
}
bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
ensureClipRegion();
clipRegion->op(region, op);
copyClipRectFromRegion();
flags |= Snapshot::kFlagClipSet;
return true;
return mClipArea->clipRegion(region, op);
}
bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
Rect r(left, top, right, bottom);
transform->mapRect(r);
return clipTransformed(r, op);
flags |= Snapshot::kFlagClipSet;
return mClipArea->clipRectWithTransform(left, top, right, bottom, transform, op);
}
bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
bool clipped = false;
switch (op) {
case SkRegion::kIntersect_Op: {
if (CC_UNLIKELY(!clipRegion->isEmpty())) {
ensureClipRegion();
clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op);
} else {
clipped = clipRect->intersect(r);
if (!clipped) {
clipRect->setEmpty();
clipped = true;
}
}
break;
}
case SkRegion::kReplace_Op: {
setClip(r.left, r.top, r.right, r.bottom);
clipped = true;
break;
}
default: {
ensureClipRegion();
clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, op);
break;
}
}
if (clipped) {
flags |= Snapshot::kFlagClipSet;
}
return clipped;
bool Snapshot::clipPath(const SkPath& path, SkRegion::Op op) {
flags |= Snapshot::kFlagClipSet;
return mClipArea->clipPathWithTransform(path, transform, op);
}
void Snapshot::setClip(float left, float top, float right, float bottom) {
clipRect->set(left, top, right, bottom);
if (!clipRegion->isEmpty()) {
clipRegion->setEmpty();
}
mClipArea->setClip(left, top, right, bottom);
flags |= Snapshot::kFlagClipSet;
}
@@ -181,7 +111,7 @@ const Rect& Snapshot::getLocalClip() {
mat4 inverse;
inverse.loadInverse(*transform);
mLocalClip.set(*clipRect);
mLocalClip.set(mClipArea->getClipRect());
inverse.mapRect(mLocalClip);
return mLocalClip;
@@ -191,8 +121,7 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) {
// TODO: This is incorrect, when we start rendering into a new layer,
// we may have to modify the previous snapshot's clip rect and clip
// region if the previous restore() call did not restore the clip
clipRect = &mClipRectRoot;
clipRegion = &mClipRegionRoot;
mClipArea = &mClipAreaRoot;
setClip(left, top, right, bottom);
}
@@ -219,7 +148,7 @@ void Snapshot::resetTransform(float x, float y, float z) {
void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds,
float radius, bool highPriority) {
if (bounds.isEmpty()) {
clipRect->setEmpty();
mClipArea->setEmpty();
return;
}
@@ -272,9 +201,10 @@ bool Snapshot::isIgnored() const {
void Snapshot::dump() const {
ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
this, flags, previous.get(), getViewportHeight(), isIgnored(), clipRegion && !clipRegion->isEmpty());
this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple());
const Rect& clipRect(mClipArea->getClipRect());
ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f",
clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
clipRect, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
ALOGD(" Transform (at %p):", transform);
transform->dump();
}

View File

@@ -26,6 +26,7 @@
#include <SkRegion.h>
#include "ClipArea.h"
#include "Layer.h"
#include "Matrix.h"
#include "Outline.h"
@@ -128,6 +129,11 @@ public:
*/
bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
/**
* Modifies the current clip with the specified path and operation.
*/
bool clipPath(const SkPath& path, SkRegion::Op op);
/**
* Sets the current clip.
*/
@@ -142,7 +148,16 @@ public:
/**
* Returns the current clip in render target coordinates.
*/
const Rect& getRenderTargetClip() { return *clipRect; }
const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
/*
* Accessor functions so that the clip area can stay private
*/
bool clipIsEmpty() const { return mClipArea->isEmpty(); }
const Rect& getClipRect() const { return mClipArea->getClipRect(); }
const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
bool clipIsSimple() const { return mClipArea->isSimple(); }
const ClipArea& getClipArea() const { return *mClipArea; }
/**
* Resets the clip to the specified rect.
@@ -156,6 +171,7 @@ public:
void initializeViewport(int width, int height) {
mViewportData.initialize(width, height);
mClipAreaRoot.setViewportDimensions(width, height);
}
int getViewportWidth() const { return mViewportData.mWidth; }
@@ -175,7 +191,7 @@ public:
/**
* Indicates whether this snapshot should be ignored. A snapshot
* is typicalled ignored if its layer is invisible or empty.
* is typically ignored if its layer is invisible or empty.
*/
bool isIgnored() const;
@@ -229,24 +245,6 @@ public:
*/
mat4* transform;
/**
* Current clip rect. The clip is stored in canvas-space coordinates,
* (screen-space coordinates in the regular case.)
*
* This is a reference to a rect owned by this snapshot or another
* snapshot. This pointer must not be freed. See ::mClipRectRoot.
*/
Rect* clipRect;
/**
* Current clip region. The clip is stored in canvas-space coordinates,
* (screen-space coordinates in the regular case.)
*
* This is a reference to a region owned by this snapshot or another
* snapshot. This pointer must not be freed. See ::mClipRegionRoot.
*/
SkRegion* clipRegion;
/**
* The ancestor layer's dirty region.
*
@@ -260,7 +258,7 @@ public:
* has translucent rendering in a non-overlapping View. This value will be used by
* the renderer to set the alpha in the current color being used for ensuing drawing
* operations. The value is inherited by child snapshots because the same value should
* be applied to descendents of the current DisplayList (for example, a TextView contains
* be applied to descendants of the current DisplayList (for example, a TextView contains
* the base alpha value which should be applied to the child DisplayLists used for drawing
* the actual text).
*/
@@ -298,16 +296,12 @@ private:
mat4 mOrthoMatrix;
};
void ensureClipRegion();
void copyClipRectFromRegion();
bool clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op);
mat4 mTransformRoot;
Rect mClipRectRoot;
Rect mLocalClip; // don't use directly, call getLocalClip() which initializes this
SkRegion mClipRegionRoot;
ClipArea mClipAreaRoot;
ClipArea* mClipArea;
Rect mLocalClip;
ViewportData mViewportData;
Vector3 mRelativeLightCenter;

View File

@@ -32,7 +32,8 @@ namespace uirenderer {
#define STENCIL_MASK_VALUE 0x1
#endif
Stencil::Stencil(): mState(kDisabled) {
Stencil::Stencil()
: mState(kDisabled) {
}
uint8_t Stencil::getStencilSize() {
@@ -56,24 +57,36 @@ void Stencil::clear() {
glClear(GL_STENCIL_BUFFER_BIT);
}
void Stencil::enableTest() {
void Stencil::enableTest(int incrementThreshold) {
if (mState != kTest) {
enable();
glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
if (incrementThreshold > 0) {
glStencilFunc(GL_EQUAL, incrementThreshold, 0xff);
} else {
glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
}
// We only want to test, let's keep everything
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilMask(0);
mState = kTest;
}
}
void Stencil::enableWrite() {
void Stencil::enableWrite(int incrementThreshold) {
if (mState != kWrite) {
enable();
glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
// The test always passes so the first two values are meaningless
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
if (incrementThreshold > 0) {
glStencilFunc(GL_ALWAYS, 1, 0xff);
// The test always passes so the first two values are meaningless
glStencilOp(GL_INCR, GL_INCR, GL_INCR);
} else {
glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
// The test always passes so the first two values are meaningless
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glStencilMask(0xff);
mState = kWrite;
}
}

View File

@@ -53,17 +53,21 @@ public:
void clear();
/**
* Enables stencil test. When the stencil test is enabled the stencil
* buffer is not written into.
* Enables stencil test. When the stencil test is enabled the stencil buffer is not written
* into. An increment threshold of zero causes the stencil to use a constant reference value
* and GL_EQUAL for the test. A non-zero increment threshold causes the stencil to use that
* value as the reference value and GL_EQUAL for the test.
*/
void enableTest();
void enableTest(int incrementThreshold);
/**
* Enables stencil write. When stencil write is enabled, the stencil
* test always succeeds and the value 0x1 is written in the stencil
* buffer for each fragment.
* buffer for each fragment. An increment threshold of zero causes the stencil to use a constant
* reference value and GL_EQUAL for the test. A non-zero increment threshold causes the stencil
* to use that value as the reference value and GL_EQUAL for the test.
*/
void enableWrite();
void enableWrite(int incrementThreshold);
/**
* The test passes only when equal to the specified value.

View File

@@ -32,3 +32,16 @@ LOCAL_SRC_FILES += \
tests/main.cpp
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk
LOCAL_MODULE := hwui_unit_tests
LOCAL_MODULE_TAGS := tests
include $(LOCAL_PATH)/Android.common.mk
LOCAL_SRC_FILES += \
tests/ClipAreaTests.cpp \
include $(BUILD_NATIVE_TEST)

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2015 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 <gtest/gtest.h>
#include <SkPath.h>
#include <SkRegion.h>
#include "ClipArea.h"
#include "Matrix.h"
#include "Rect.h"
#include "utils/LinearAllocator.h"
namespace android {
namespace uirenderer {
static Rect kViewportBounds(0, 0, 2048, 2048);
static ClipArea createClipArea() {
ClipArea area;
area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight());
return area;
}
TEST(TransformedRectangle, basics) {
Rect r(0, 0, 100, 100);
Matrix4 minus90;
minus90.loadRotate(-90);
minus90.mapRect(r);
Rect r2(20, 40, 120, 60);
Matrix4 m90;
m90.loadRotate(90);
TransformedRectangle tr(r, m90);
EXPECT_TRUE(tr.canSimplyIntersectWith(tr));
Matrix4 m0;
TransformedRectangle tr0(r2, m0);
EXPECT_FALSE(tr.canSimplyIntersectWith(tr0));
Matrix4 m45;
m45.loadRotate(45);
TransformedRectangle tr2(r, m45);
EXPECT_FALSE(tr2.canSimplyIntersectWith(tr));
}
TEST(RectangleList, basics) {
RectangleList list;
EXPECT_TRUE(list.isEmpty());
Rect r(0, 0, 100, 100);
Matrix4 m45;
m45.loadRotate(45);
list.set(r, m45);
EXPECT_FALSE(list.isEmpty());
Rect r2(20, 20, 200, 200);
list.intersectWith(r2, m45);
EXPECT_FALSE(list.isEmpty());
EXPECT_EQ(1, list.getTransformedRectanglesCount());
Rect r3(20, 20, 200, 200);
Matrix4 m30;
m30.loadRotate(30);
list.intersectWith(r2, m30);
EXPECT_FALSE(list.isEmpty());
EXPECT_EQ(2, list.getTransformedRectanglesCount());
SkRegion clip;
clip.setRect(0, 0, 2000, 2000);
SkRegion rgn(list.convertToRegion(clip));
EXPECT_FALSE(rgn.isEmpty());
}
TEST(ClipArea, basics) {
ClipArea area(createClipArea());
EXPECT_FALSE(area.isEmpty());
}
TEST(ClipArea, paths) {
ClipArea area(createClipArea());
Matrix4 transform;
transform.loadIdentity();
SkPath path;
SkScalar r = 100;
path.addCircle(r, r, r);
area.clipPathWithTransform(path, &transform, SkRegion::kIntersect_Op);
EXPECT_FALSE(area.isEmpty());
EXPECT_FALSE(area.isSimple());
EXPECT_FALSE(area.isRectangleList());
Rect clipRect(area.getClipRect());
clipRect.dump("clipRect");
Rect expected(0, 0, r * 2, r * 2);
expected.dump("expected");
EXPECT_EQ(expected, clipRect);
SkRegion clipRegion(area.getClipRegion());
auto skRect(clipRegion.getBounds());
Rect regionBounds;
regionBounds.set(skRect);
EXPECT_EQ(expected, regionBounds);
}
}
}

View File

@@ -17,6 +17,8 @@
#ifndef ANDROID_HWUI_PAIR_H
#define ANDROID_HWUI_PAIR_H
#include <utils/TypeHelpers.h>
namespace android {
namespace uirenderer {