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:
@@ -18,6 +18,7 @@ LOCAL_SRC_FILES := \
|
||||
AssetAtlas.cpp \
|
||||
Caches.cpp \
|
||||
CanvasState.cpp \
|
||||
ClipArea.cpp \
|
||||
DamageAccumulator.cpp \
|
||||
DisplayList.cpp \
|
||||
DeferredDisplayList.cpp \
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
368
libs/hwui/ClipArea.cpp
Normal 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
175
libs/hwui/ClipArea.h
Normal 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
141
libs/hwui/OpenGLRenderer.cpp
Executable file → Normal 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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
116
libs/hwui/tests/ClipAreaTests.cpp
Normal file
116
libs/hwui/tests/ClipAreaTests.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef ANDROID_HWUI_PAIR_H
|
||||
#define ANDROID_HWUI_PAIR_H
|
||||
|
||||
#include <utils/TypeHelpers.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user