ClipArea.cpp revision 02806288d1c56475413888a934c796e6e4eb11c5
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16#include "ClipArea.h" 17 18#include "utils/LinearAllocator.h" 19 20#include <SkPath.h> 21#include <limits> 22#include <type_traits> 23 24namespace android { 25namespace uirenderer { 26 27static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { 28 Vertex v = {x, y}; 29 transform.mapPoint(v.x, v.y); 30 transformedBounds.expandToCover(v.x, v.y); 31} 32 33Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { 34 const float kMinFloat = std::numeric_limits<float>::lowest(); 35 const float kMaxFloat = std::numeric_limits<float>::max(); 36 Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat }; 37 handlePoint(transformedBounds, transform, r.left, r.top); 38 handlePoint(transformedBounds, transform, r.right, r.top); 39 handlePoint(transformedBounds, transform, r.left, r.bottom); 40 handlePoint(transformedBounds, transform, r.right, r.bottom); 41 return transformedBounds; 42} 43 44void ClipBase::dump() const { 45 ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect)); 46} 47 48/* 49 * TransformedRectangle 50 */ 51 52TransformedRectangle::TransformedRectangle() { 53} 54 55TransformedRectangle::TransformedRectangle(const Rect& bounds, 56 const Matrix4& transform) 57 : mBounds(bounds) 58 , mTransform(transform) { 59} 60 61bool TransformedRectangle::canSimplyIntersectWith( 62 const TransformedRectangle& other) const { 63 64 return mTransform == other.mTransform; 65} 66 67void TransformedRectangle::intersectWith(const TransformedRectangle& other) { 68 mBounds.doIntersect(other.mBounds); 69} 70 71bool TransformedRectangle::isEmpty() const { 72 return mBounds.isEmpty(); 73} 74 75/* 76 * RectangleList 77 */ 78 79RectangleList::RectangleList() 80 : mTransformedRectanglesCount(0) { 81} 82 83bool RectangleList::isEmpty() const { 84 if (mTransformedRectanglesCount < 1) { 85 return true; 86 } 87 88 for (int i = 0; i < mTransformedRectanglesCount; i++) { 89 if (mTransformedRectangles[i].isEmpty()) { 90 return true; 91 } 92 } 93 return false; 94} 95 96int RectangleList::getTransformedRectanglesCount() const { 97 return mTransformedRectanglesCount; 98} 99 100const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const { 101 return mTransformedRectangles[i]; 102} 103 104void RectangleList::setEmpty() { 105 mTransformedRectanglesCount = 0; 106} 107 108void RectangleList::set(const Rect& bounds, const Matrix4& transform) { 109 mTransformedRectanglesCount = 1; 110 mTransformedRectangles[0] = TransformedRectangle(bounds, transform); 111} 112 113bool RectangleList::intersectWith(const Rect& bounds, 114 const Matrix4& transform) { 115 TransformedRectangle newRectangle(bounds, transform); 116 117 // Try to find a rectangle with a compatible transformation 118 int index = 0; 119 for (; index < mTransformedRectanglesCount; index++) { 120 TransformedRectangle& tr(mTransformedRectangles[index]); 121 if (tr.canSimplyIntersectWith(newRectangle)) { 122 tr.intersectWith(newRectangle); 123 return true; 124 } 125 } 126 127 // Add it to the list if there is room 128 if (index < kMaxTransformedRectangles) { 129 mTransformedRectangles[index] = newRectangle; 130 mTransformedRectanglesCount += 1; 131 return true; 132 } 133 134 // This rectangle list is full 135 return false; 136} 137 138Rect RectangleList::calculateBounds() const { 139 Rect bounds; 140 for (int index = 0; index < mTransformedRectanglesCount; index++) { 141 const TransformedRectangle& tr(mTransformedRectangles[index]); 142 if (index == 0) { 143 bounds = tr.transformedBounds(); 144 } else { 145 bounds.doIntersect(tr.transformedBounds()); 146 } 147 } 148 return bounds; 149} 150 151static SkPath pathFromTransformedRectangle(const Rect& bounds, 152 const Matrix4& transform) { 153 SkPath rectPath; 154 SkPath rectPathTransformed; 155 rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom); 156 SkMatrix skTransform; 157 transform.copyTo(skTransform); 158 rectPath.transform(skTransform, &rectPathTransformed); 159 return rectPathTransformed; 160} 161 162SkRegion RectangleList::convertToRegion(const SkRegion& clip) const { 163 SkRegion rectangleListAsRegion; 164 for (int index = 0; index < mTransformedRectanglesCount; index++) { 165 const TransformedRectangle& tr(mTransformedRectangles[index]); 166 SkPath rectPathTransformed = pathFromTransformedRectangle( 167 tr.getBounds(), tr.getTransform()); 168 if (index == 0) { 169 rectangleListAsRegion.setPath(rectPathTransformed, clip); 170 } else { 171 SkRegion rectRegion; 172 rectRegion.setPath(rectPathTransformed, clip); 173 rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op); 174 } 175 } 176 return rectangleListAsRegion; 177} 178 179void RectangleList::transform(const Matrix4& transform) { 180 for (int index = 0; index < mTransformedRectanglesCount; index++) { 181 mTransformedRectangles[index].transform(transform); 182 } 183} 184 185/* 186 * ClipArea 187 */ 188 189ClipArea::ClipArea() 190 : mMode(ClipMode::Rectangle) { 191} 192 193/* 194 * Interface 195 */ 196 197void ClipArea::setViewportDimensions(int width, int height) { 198 mPostViewportClipObserved = false; 199 mViewportBounds.set(0, 0, width, height); 200 mClipRect = mViewportBounds; 201} 202 203void ClipArea::setEmpty() { 204 onClipUpdated(); 205 mMode = ClipMode::Rectangle; 206 mClipRect.setEmpty(); 207 mClipRegion.setEmpty(); 208 mRectangleList.setEmpty(); 209} 210 211void ClipArea::setClip(float left, float top, float right, float bottom) { 212 onClipUpdated(); 213 mMode = ClipMode::Rectangle; 214 mClipRect.set(left, top, right, bottom); 215 mClipRegion.setEmpty(); 216} 217 218void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, 219 SkRegion::Op op) { 220 if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; 221 onClipUpdated(); 222 switch (mMode) { 223 case ClipMode::Rectangle: 224 rectangleModeClipRectWithTransform(r, transform, op); 225 break; 226 case ClipMode::RectangleList: 227 rectangleListModeClipRectWithTransform(r, transform, op); 228 break; 229 case ClipMode::Region: 230 regionModeClipRectWithTransform(r, transform, op); 231 break; 232 } 233} 234 235void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) { 236 if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; 237 onClipUpdated(); 238 enterRegionMode(); 239 mClipRegion.op(region, op); 240 onClipRegionUpdated(); 241} 242 243void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, 244 SkRegion::Op op) { 245 if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; 246 onClipUpdated(); 247 SkMatrix skTransform; 248 transform->copyTo(skTransform); 249 SkPath transformed; 250 path.transform(skTransform, &transformed); 251 SkRegion region; 252 regionFromPath(transformed, region); 253 clipRegion(region, op); 254} 255 256/* 257 * Rectangle mode 258 */ 259 260void ClipArea::enterRectangleMode() { 261 // Entering rectangle mode discards any 262 // existing clipping information from the other modes. 263 // The only way this occurs is by a clip setting operation. 264 mMode = ClipMode::Rectangle; 265} 266 267void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, 268 const mat4* transform, SkRegion::Op op) { 269 270 if (op == SkRegion::kReplace_Op && transform->rectToRect()) { 271 mClipRect = r; 272 transform->mapRect(mClipRect); 273 return; 274 } else if (op != SkRegion::kIntersect_Op) { 275 enterRegionMode(); 276 regionModeClipRectWithTransform(r, transform, op); 277 return; 278 } 279 280 if (transform->rectToRect()) { 281 Rect transformed(r); 282 transform->mapRect(transformed); 283 mClipRect.doIntersect(transformed); 284 return; 285 } 286 287 enterRectangleListMode(); 288 rectangleListModeClipRectWithTransform(r, transform, op); 289} 290 291/* 292 * RectangleList mode implementation 293 */ 294 295void ClipArea::enterRectangleListMode() { 296 // Is is only legal to enter rectangle list mode from 297 // rectangle mode, since rectangle list mode cannot represent 298 // all clip areas that can be represented by a region. 299 ALOG_ASSERT(mMode == ClipMode::Rectangle); 300 mMode = ClipMode::RectangleList; 301 mRectangleList.set(mClipRect, Matrix4::identity()); 302} 303 304void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, 305 const mat4* transform, SkRegion::Op op) { 306 if (op != SkRegion::kIntersect_Op 307 || !mRectangleList.intersectWith(r, *transform)) { 308 enterRegionMode(); 309 regionModeClipRectWithTransform(r, transform, op); 310 } 311} 312 313/* 314 * Region mode implementation 315 */ 316 317void ClipArea::enterRegionMode() { 318 ClipMode oldMode = mMode; 319 mMode = ClipMode::Region; 320 if (oldMode != ClipMode::Region) { 321 if (oldMode == ClipMode::Rectangle) { 322 mClipRegion.setRect(mClipRect.toSkIRect()); 323 } else { 324 mClipRegion = mRectangleList.convertToRegion(createViewportRegion()); 325 onClipRegionUpdated(); 326 } 327 } 328} 329 330void ClipArea::regionModeClipRectWithTransform(const Rect& r, 331 const mat4* transform, SkRegion::Op op) { 332 SkPath transformedRect = pathFromTransformedRectangle(r, *transform); 333 SkRegion transformedRectRegion; 334 regionFromPath(transformedRect, transformedRectRegion); 335 mClipRegion.op(transformedRectRegion, op); 336 onClipRegionUpdated(); 337} 338 339void ClipArea::onClipRegionUpdated() { 340 if (!mClipRegion.isEmpty()) { 341 mClipRect.set(mClipRegion.getBounds()); 342 343 if (mClipRegion.isRect()) { 344 mClipRegion.setEmpty(); 345 enterRectangleMode(); 346 } 347 } else { 348 mClipRect.setEmpty(); 349 } 350} 351 352/** 353 * Clip serialization 354 */ 355 356const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) { 357 if (!mPostViewportClipObserved) { 358 // Only initial clip-to-viewport observed, so no serialization of clip necessary 359 return nullptr; 360 } 361 362 static_assert(std::is_trivially_destructible<Rect>::value, 363 "expect Rect to be trivially destructible"); 364 static_assert(std::is_trivially_destructible<RectangleList>::value, 365 "expect RectangleList to be trivially destructible"); 366 367 if (mLastSerialization == nullptr) { 368 ClipBase* serialization = nullptr; 369 switch (mMode) { 370 case ClipMode::Rectangle: 371 serialization = allocator.create<ClipRect>(mClipRect); 372 break; 373 case ClipMode::RectangleList: 374 serialization = allocator.create<ClipRectList>(mRectangleList); 375 serialization->rect = mRectangleList.calculateBounds(); 376 break; 377 case ClipMode::Region: 378 serialization = allocator.create<ClipRegion>(mClipRegion); 379 serialization->rect.set(mClipRegion.getBounds()); 380 break; 381 } 382 // TODO: this is only done for draw time, should eventually avoid for record time 383 serialization->rect.snapToPixelBoundaries(); 384 mLastSerialization = serialization; 385 } 386 return mLastSerialization; 387} 388 389inline static const RectangleList& getRectList(const ClipBase* scb) { 390 return reinterpret_cast<const ClipRectList*>(scb)->rectList; 391} 392 393inline static const SkRegion& getRegion(const ClipBase* scb) { 394 return reinterpret_cast<const ClipRegion*>(scb)->region; 395} 396 397// Conservative check for too many rectangles to fit in rectangle list. 398// For simplicity, doesn't account for rect merging 399static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) { 400 int currentRectCount = clipArea.isRectangleList() 401 ? clipArea.getRectangleList().getTransformedRectanglesCount() 402 : 1; 403 int recordedRectCount = (scb->mode == ClipMode::RectangleList) 404 ? getRectList(scb).getTransformedRectanglesCount() 405 : 1; 406 return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles; 407} 408 409static const ClipRect sEmptyClipRect(Rect(0, 0)); 410 411const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator, 412 const ClipBase* recordedClip, const Matrix4& recordedClipTransform) { 413 414 // if no recordedClip passed, just serialize current state 415 if (!recordedClip) return serializeClip(allocator); 416 417 // if either is empty, clip is empty 418 if (CC_UNLIKELY(recordedClip->rect.isEmpty())|| mClipRect.isEmpty()) return &sEmptyClipRect; 419 420 if (!mLastResolutionResult 421 || recordedClip != mLastResolutionClip 422 || recordedClipTransform != mLastResolutionTransform) { 423 mLastResolutionClip = recordedClip; 424 mLastResolutionTransform = recordedClipTransform; 425 426 if (CC_LIKELY(mMode == ClipMode::Rectangle 427 && recordedClip->mode == ClipMode::Rectangle 428 && recordedClipTransform.rectToRect())) { 429 // common case - result is a single rectangle 430 auto rectClip = allocator.create<ClipRect>(recordedClip->rect); 431 recordedClipTransform.mapRect(rectClip->rect); 432 rectClip->rect.doIntersect(mClipRect); 433 rectClip->rect.snapToPixelBoundaries(); 434 mLastResolutionResult = rectClip; 435 } else if (CC_UNLIKELY(mMode == ClipMode::Region 436 || recordedClip->mode == ClipMode::Region 437 || cannotFitInRectangleList(*this, recordedClip))) { 438 // region case 439 SkRegion other; 440 switch (recordedClip->mode) { 441 case ClipMode::Rectangle: 442 if (CC_LIKELY(recordedClipTransform.rectToRect())) { 443 // simple transform, skip creating SkPath 444 Rect resultClip(recordedClip->rect); 445 recordedClipTransform.mapRect(resultClip); 446 other.setRect(resultClip.toSkIRect()); 447 } else { 448 SkPath transformedRect = pathFromTransformedRectangle(recordedClip->rect, 449 recordedClipTransform); 450 other.setPath(transformedRect, createViewportRegion()); 451 } 452 break; 453 case ClipMode::RectangleList: { 454 RectangleList transformedList(getRectList(recordedClip)); 455 transformedList.transform(recordedClipTransform); 456 other = transformedList.convertToRegion(createViewportRegion()); 457 break; 458 } 459 case ClipMode::Region: 460 other = getRegion(recordedClip); 461 462 // TODO: handle non-translate transforms properly! 463 other.translate(recordedClipTransform.getTranslateX(), 464 recordedClipTransform.getTranslateY()); 465 } 466 467 ClipRegion* regionClip = allocator.create<ClipRegion>(); 468 switch (mMode) { 469 case ClipMode::Rectangle: 470 regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op); 471 break; 472 case ClipMode::RectangleList: 473 regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()), 474 other, SkRegion::kIntersect_Op); 475 break; 476 case ClipMode::Region: 477 regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op); 478 break; 479 } 480 // Don't need to snap, since region's in int bounds 481 regionClip->rect.set(regionClip->region.getBounds()); 482 mLastResolutionResult = regionClip; 483 } else { 484 auto rectListClip = allocator.create<ClipRectList>(mRectangleList); 485 auto&& rectList = rectListClip->rectList; 486 if (mMode == ClipMode::Rectangle) { 487 rectList.set(mClipRect, Matrix4::identity()); 488 } 489 490 if (recordedClip->mode == ClipMode::Rectangle) { 491 rectList.intersectWith(recordedClip->rect, recordedClipTransform); 492 } else { 493 const RectangleList& other = getRectList(recordedClip); 494 for (int i = 0; i < other.getTransformedRectanglesCount(); i++) { 495 auto&& tr = other.getTransformedRectangle(i); 496 Matrix4 totalTransform(recordedClipTransform); 497 totalTransform.multiply(tr.getTransform()); 498 rectList.intersectWith(tr.getBounds(), totalTransform); 499 } 500 } 501 rectListClip->rect = rectList.calculateBounds(); 502 rectListClip->rect.snapToPixelBoundaries(); 503 mLastResolutionResult = rectListClip; 504 } 505 } 506 return mLastResolutionResult; 507} 508 509void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) { 510 if (!clip) return; // nothing to do 511 512 if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) { 513 clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op); 514 } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { 515 auto&& rectList = getRectList(clip); 516 for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) { 517 auto&& tr = rectList.getTransformedRectangle(i); 518 Matrix4 totalTransform(transform); 519 totalTransform.multiply(tr.getTransform()); 520 clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op); 521 } 522 } else { 523 SkRegion region(getRegion(clip)); 524 // TODO: handle non-translate transforms properly! 525 region.translate(transform.getTranslateX(), transform.getTranslateY()); 526 clipRegion(region, SkRegion::kIntersect_Op); 527 } 528} 529 530} /* namespace uirenderer */ 531} /* namespace android */ 532