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