1 /* 2 * Copyright (C) 2014 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 17 #include "RenderNode.h" 18 19 #include "DamageAccumulator.h" 20 #include "Debug.h" 21 #include "TreeInfo.h" 22 #include "VectorDrawable.h" 23 #include "renderstate/RenderState.h" 24 #include "renderthread/CanvasContext.h" 25 #include "utils/FatVector.h" 26 #include "utils/MathUtils.h" 27 #include "utils/StringUtils.h" 28 #include "utils/TraceUtils.h" 29 30 #include <SkPathOps.h> 31 #include <algorithm> 32 #include <atomic> 33 #include <sstream> 34 #include <string> 35 36 namespace android { 37 namespace uirenderer { 38 39 // Used for tree mutations that are purely destructive. 40 // Generic tree mutations should use MarkAndSweepObserver instead 41 class ImmediateRemoved : public TreeObserver { 42 public: ImmediateRemoved(TreeInfo * info)43 explicit ImmediateRemoved(TreeInfo* info) : mTreeInfo(info) {} 44 onMaybeRemovedFromTree(RenderNode * node)45 void onMaybeRemovedFromTree(RenderNode* node) override { node->onRemovedFromTree(mTreeInfo); } 46 47 private: 48 TreeInfo* mTreeInfo; 49 }; 50 generateId()51 static int64_t generateId() { 52 static std::atomic<int64_t> sNextId{1}; 53 return sNextId++; 54 } 55 RenderNode()56 RenderNode::RenderNode() 57 : mUniqueId(generateId()) 58 , mDirtyPropertyFields(0) 59 , mNeedsDisplayListSync(false) 60 , mDisplayList(nullptr) 61 , mStagingDisplayList(nullptr) 62 , mAnimatorManager(*this) 63 , mParentCount(0) {} 64 ~RenderNode()65 RenderNode::~RenderNode() { 66 ImmediateRemoved observer(nullptr); 67 deleteDisplayList(observer); 68 delete mStagingDisplayList; 69 LOG_ALWAYS_FATAL_IF(hasLayer(), "layer missed detachment!"); 70 } 71 setStagingDisplayList(DisplayList * displayList)72 void RenderNode::setStagingDisplayList(DisplayList* displayList) { 73 mValid = (displayList != nullptr); 74 mNeedsDisplayListSync = true; 75 delete mStagingDisplayList; 76 mStagingDisplayList = displayList; 77 } 78 79 /** 80 * This function is a simplified version of replay(), where we simply retrieve and log the 81 * display list. This function should remain in sync with the replay() function. 82 */ output()83 void RenderNode::output() { 84 LogcatStream strout; 85 strout << "Root"; 86 output(strout, 0); 87 } 88 output(std::ostream & output,uint32_t level)89 void RenderNode::output(std::ostream& output, uint32_t level) { 90 output << " (" << getName() << " " << this 91 << (MathUtils::isZero(properties().getAlpha()) ? ", zero alpha" : "") 92 << (properties().hasShadow() ? ", casting shadow" : "") 93 << (isRenderable() ? "" : ", empty") 94 << (properties().getProjectBackwards() ? ", projected" : "") 95 << (hasLayer() ? ", on HW Layer" : "") << ")" << std::endl; 96 97 properties().debugOutputProperties(output, level + 1); 98 99 if (mDisplayList) { 100 mDisplayList->output(output, level); 101 } 102 output << std::string(level * 2, ' ') << "/RenderNode(" << getName() << " " << this << ")"; 103 output << std::endl; 104 } 105 getDebugSize()106 int RenderNode::getDebugSize() { 107 int size = sizeof(RenderNode); 108 if (mStagingDisplayList) { 109 size += mStagingDisplayList->getUsedSize(); 110 } 111 if (mDisplayList && mDisplayList != mStagingDisplayList) { 112 size += mDisplayList->getUsedSize(); 113 } 114 return size; 115 } 116 prepareTree(TreeInfo & info)117 void RenderNode::prepareTree(TreeInfo& info) { 118 ATRACE_CALL(); 119 LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing"); 120 MarkAndSweepRemoved observer(&info); 121 122 const int before = info.disableForceDark; 123 prepareTreeImpl(observer, info, false); 124 LOG_ALWAYS_FATAL_IF(before != info.disableForceDark, "Mis-matched force dark"); 125 } 126 addAnimator(const sp<BaseRenderNodeAnimator> & animator)127 void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) { 128 mAnimatorManager.addAnimator(animator); 129 } 130 removeAnimator(const sp<BaseRenderNodeAnimator> & animator)131 void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) { 132 mAnimatorManager.removeAnimator(animator); 133 } 134 damageSelf(TreeInfo & info)135 void RenderNode::damageSelf(TreeInfo& info) { 136 if (isRenderable()) { 137 mDamageGenerationId = info.damageGenerationId; 138 if (properties().getClipDamageToBounds()) { 139 info.damageAccumulator->dirty(0, 0, properties().getWidth(), properties().getHeight()); 140 } else { 141 // Hope this is big enough? 142 // TODO: Get this from the display list ops or something 143 info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); 144 } 145 } 146 } 147 prepareLayer(TreeInfo & info,uint32_t dirtyMask)148 void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { 149 LayerType layerType = properties().effectiveLayerType(); 150 if (CC_UNLIKELY(layerType == LayerType::RenderLayer)) { 151 // Damage applied so far needs to affect our parent, but does not require 152 // the layer to be updated. So we pop/push here to clear out the current 153 // damage and get a clean state for display list or children updates to 154 // affect, which will require the layer to be updated 155 info.damageAccumulator->popTransform(); 156 info.damageAccumulator->pushTransform(this); 157 if (dirtyMask & DISPLAY_LIST) { 158 damageSelf(info); 159 } 160 } 161 } 162 pushLayerUpdate(TreeInfo & info)163 void RenderNode::pushLayerUpdate(TreeInfo& info) { 164 LayerType layerType = properties().effectiveLayerType(); 165 // If we are not a layer OR we cannot be rendered (eg, view was detached) 166 // we need to destroy any Layers we may have had previously 167 if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) || 168 CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) || 169 CC_UNLIKELY(!properties().fitsOnLayer())) { 170 if (CC_UNLIKELY(hasLayer())) { 171 this->setLayerSurface(nullptr); 172 } 173 return; 174 } 175 176 if (info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator, info.errorHandler)) { 177 damageSelf(info); 178 } 179 180 if (!hasLayer()) { 181 return; 182 } 183 184 SkRect dirty; 185 info.damageAccumulator->peekAtDirty(&dirty); 186 info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty); 187 188 // There might be prefetched layers that need to be accounted for. 189 // That might be us, so tell CanvasContext that this layer is in the 190 // tree and should not be destroyed. 191 info.canvasContext.markLayerInUse(this); 192 } 193 194 /** 195 * Traverse down the the draw tree to prepare for a frame. 196 * 197 * MODE_FULL = UI Thread-driven (thus properties must be synced), otherwise RT driven 198 * 199 * While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the 200 * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer. 201 */ prepareTreeImpl(TreeObserver & observer,TreeInfo & info,bool functorsNeedLayer)202 void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) { 203 if (mDamageGenerationId == info.damageGenerationId) { 204 // We hit the same node a second time in the same tree. We don't know the minimal 205 // damage rect anymore, so just push the biggest we can onto our parent's transform 206 // We push directly onto parent in case we are clipped to bounds but have moved position. 207 info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); 208 } 209 info.damageAccumulator->pushTransform(this); 210 211 if (info.mode == TreeInfo::MODE_FULL) { 212 pushStagingPropertiesChanges(info); 213 } 214 215 if (!mProperties.getAllowForceDark()) { 216 info.disableForceDark++; 217 } 218 219 uint32_t animatorDirtyMask = 0; 220 if (CC_LIKELY(info.runAnimations)) { 221 animatorDirtyMask = mAnimatorManager.animate(info); 222 } 223 224 bool willHaveFunctor = false; 225 if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) { 226 willHaveFunctor = mStagingDisplayList->hasFunctor(); 227 } else if (mDisplayList) { 228 willHaveFunctor = mDisplayList->hasFunctor(); 229 } 230 bool childFunctorsNeedLayer = 231 mProperties.prepareForFunctorPresence(willHaveFunctor, functorsNeedLayer); 232 233 if (CC_UNLIKELY(mPositionListener.get())) { 234 mPositionListener->onPositionUpdated(*this, info); 235 } 236 237 prepareLayer(info, animatorDirtyMask); 238 if (info.mode == TreeInfo::MODE_FULL) { 239 pushStagingDisplayListChanges(observer, info); 240 } 241 242 if (mDisplayList) { 243 info.out.hasFunctors |= mDisplayList->hasFunctor(); 244 bool isDirty = mDisplayList->prepareListAndChildren( 245 observer, info, childFunctorsNeedLayer, 246 [](RenderNode* child, TreeObserver& observer, TreeInfo& info, 247 bool functorsNeedLayer) { 248 child->prepareTreeImpl(observer, info, functorsNeedLayer); 249 }); 250 if (isDirty) { 251 damageSelf(info); 252 } 253 } 254 pushLayerUpdate(info); 255 256 if (!mProperties.getAllowForceDark()) { 257 info.disableForceDark--; 258 } 259 info.damageAccumulator->popTransform(); 260 } 261 syncProperties()262 void RenderNode::syncProperties() { 263 mProperties = mStagingProperties; 264 } 265 pushStagingPropertiesChanges(TreeInfo & info)266 void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { 267 if (mPositionListenerDirty) { 268 mPositionListener = std::move(mStagingPositionListener); 269 mStagingPositionListener = nullptr; 270 mPositionListenerDirty = false; 271 } 272 273 // Push the animators first so that setupStartValueIfNecessary() is called 274 // before properties() is trampled by stagingProperties(), as they are 275 // required by some animators. 276 if (CC_LIKELY(info.runAnimations)) { 277 mAnimatorManager.pushStaging(); 278 } 279 if (mDirtyPropertyFields) { 280 mDirtyPropertyFields = 0; 281 damageSelf(info); 282 info.damageAccumulator->popTransform(); 283 syncProperties(); 284 // We could try to be clever and only re-damage if the matrix changed. 285 // However, we don't need to worry about that. The cost of over-damaging 286 // here is only going to be a single additional map rect of this node 287 // plus a rect join(). The parent's transform (and up) will only be 288 // performed once. 289 info.damageAccumulator->pushTransform(this); 290 damageSelf(info); 291 } 292 } 293 syncDisplayList(TreeObserver & observer,TreeInfo * info)294 void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { 295 // Make sure we inc first so that we don't fluctuate between 0 and 1, 296 // which would thrash the layer cache 297 if (mStagingDisplayList) { 298 mStagingDisplayList->updateChildren([](RenderNode* child) { child->incParentRefCount(); }); 299 } 300 deleteDisplayList(observer, info); 301 mDisplayList = mStagingDisplayList; 302 mStagingDisplayList = nullptr; 303 if (mDisplayList) { 304 WebViewSyncData syncData { 305 .applyForceDark = info && !info->disableForceDark 306 }; 307 mDisplayList->syncContents(syncData); 308 handleForceDark(info); 309 } 310 } 311 handleForceDark(android::uirenderer::TreeInfo * info)312 void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { 313 if (CC_LIKELY(!info || info->disableForceDark)) { 314 return; 315 } 316 auto usage = usageHint(); 317 const auto& children = mDisplayList->mChildNodes; 318 if (mDisplayList->hasText()) { 319 usage = UsageHint::Foreground; 320 } 321 if (usage == UsageHint::Unknown) { 322 if (children.size() > 1) { 323 usage = UsageHint::Background; 324 } else if (children.size() == 1 && 325 children.front().getRenderNode()->usageHint() != 326 UsageHint::Background) { 327 usage = UsageHint::Background; 328 } 329 } 330 if (children.size() > 1) { 331 // Crude overlap check 332 SkRect drawn = SkRect::MakeEmpty(); 333 for (auto iter = children.rbegin(); iter != children.rend(); ++iter) { 334 const auto& child = iter->getRenderNode(); 335 // We use stagingProperties here because we haven't yet sync'd the children 336 SkRect bounds = SkRect::MakeXYWH(child->stagingProperties().getX(), child->stagingProperties().getY(), 337 child->stagingProperties().getWidth(), child->stagingProperties().getHeight()); 338 if (bounds.contains(drawn)) { 339 // This contains everything drawn after it, so make it a background 340 child->setUsageHint(UsageHint::Background); 341 } 342 drawn.join(bounds); 343 } 344 } 345 mDisplayList->mDisplayList.applyColorTransform( 346 usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light); 347 } 348 pushStagingDisplayListChanges(TreeObserver & observer,TreeInfo & info)349 void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) { 350 if (mNeedsDisplayListSync) { 351 mNeedsDisplayListSync = false; 352 // Damage with the old display list first then the new one to catch any 353 // changes in isRenderable or, in the future, bounds 354 damageSelf(info); 355 syncDisplayList(observer, &info); 356 damageSelf(info); 357 } 358 } 359 deleteDisplayList(TreeObserver & observer,TreeInfo * info)360 void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) { 361 if (mDisplayList) { 362 mDisplayList->updateChildren( 363 [&observer, info](RenderNode* child) { child->decParentRefCount(observer, info); }); 364 if (!mDisplayList->reuseDisplayList(this, info ? &info->canvasContext : nullptr)) { 365 delete mDisplayList; 366 } 367 } 368 mDisplayList = nullptr; 369 } 370 destroyHardwareResources(TreeInfo * info)371 void RenderNode::destroyHardwareResources(TreeInfo* info) { 372 if (hasLayer()) { 373 this->setLayerSurface(nullptr); 374 } 375 setStagingDisplayList(nullptr); 376 377 ImmediateRemoved observer(info); 378 deleteDisplayList(observer, info); 379 } 380 destroyLayers()381 void RenderNode::destroyLayers() { 382 if (hasLayer()) { 383 this->setLayerSurface(nullptr); 384 } 385 if (mDisplayList) { 386 mDisplayList->updateChildren([](RenderNode* child) { child->destroyLayers(); }); 387 } 388 } 389 decParentRefCount(TreeObserver & observer,TreeInfo * info)390 void RenderNode::decParentRefCount(TreeObserver& observer, TreeInfo* info) { 391 LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!"); 392 mParentCount--; 393 if (!mParentCount) { 394 observer.onMaybeRemovedFromTree(this); 395 if (CC_UNLIKELY(mPositionListener.get())) { 396 mPositionListener->onPositionLost(*this, info); 397 } 398 } 399 } 400 onRemovedFromTree(TreeInfo * info)401 void RenderNode::onRemovedFromTree(TreeInfo* info) { 402 destroyHardwareResources(info); 403 } 404 clearRoot()405 void RenderNode::clearRoot() { 406 ImmediateRemoved observer(nullptr); 407 decParentRefCount(observer); 408 } 409 410 /** 411 * Apply property-based transformations to input matrix 412 * 413 * If true3dTransform is set to true, the transform applied to the input matrix will use true 4x4 414 * matrix computation instead of the Skia 3x3 matrix + camera hackery. 415 */ applyViewPropertyTransforms(mat4 & matrix,bool true3dTransform) const416 void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) const { 417 if (properties().getLeft() != 0 || properties().getTop() != 0) { 418 matrix.translate(properties().getLeft(), properties().getTop()); 419 } 420 if (properties().getStaticMatrix()) { 421 mat4 stat(*properties().getStaticMatrix()); 422 matrix.multiply(stat); 423 } else if (properties().getAnimationMatrix()) { 424 mat4 anim(*properties().getAnimationMatrix()); 425 matrix.multiply(anim); 426 } 427 428 bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getZ()); 429 if (properties().hasTransformMatrix() || applyTranslationZ) { 430 if (properties().isTransformTranslateOnly()) { 431 matrix.translate(properties().getTranslationX(), properties().getTranslationY(), 432 true3dTransform ? properties().getZ() : 0.0f); 433 } else { 434 if (!true3dTransform) { 435 matrix.multiply(*properties().getTransformMatrix()); 436 } else { 437 mat4 true3dMat; 438 true3dMat.loadTranslate(properties().getPivotX() + properties().getTranslationX(), 439 properties().getPivotY() + properties().getTranslationY(), 440 properties().getZ()); 441 true3dMat.rotate(properties().getRotationX(), 1, 0, 0); 442 true3dMat.rotate(properties().getRotationY(), 0, 1, 0); 443 true3dMat.rotate(properties().getRotation(), 0, 0, 1); 444 true3dMat.scale(properties().getScaleX(), properties().getScaleY(), 1); 445 true3dMat.translate(-properties().getPivotX(), -properties().getPivotY()); 446 447 matrix.multiply(true3dMat); 448 } 449 } 450 } 451 } 452 getClippedOutline(const SkRect & clipRect) const453 const SkPath* RenderNode::getClippedOutline(const SkRect& clipRect) const { 454 const SkPath* outlinePath = properties().getOutline().getPath(); 455 const uint32_t outlineID = outlinePath->getGenerationID(); 456 457 if (outlineID != mClippedOutlineCache.outlineID || clipRect != mClippedOutlineCache.clipRect) { 458 // update the cache keys 459 mClippedOutlineCache.outlineID = outlineID; 460 mClippedOutlineCache.clipRect = clipRect; 461 462 // update the cache value by recomputing a new path 463 SkPath clipPath; 464 clipPath.addRect(clipRect); 465 Op(*outlinePath, clipPath, kIntersect_SkPathOp, &mClippedOutlineCache.clippedOutline); 466 } 467 return &mClippedOutlineCache.clippedOutline; 468 } 469 470 using StringBuffer = FatVector<char, 128>; 471 472 template <typename... T> 473 // TODO:__printflike(2, 3) 474 // Doesn't work because the warning doesn't understand string_view and doesn't like that 475 // it's not a C-style variadic function. format(StringBuffer & buffer,const std::string_view & format,T...args)476 static void format(StringBuffer& buffer, const std::string_view& format, T... args) { 477 buffer.resize(buffer.capacity()); 478 while (1) { 479 int needed = snprintf(buffer.data(), buffer.size(), 480 format.data(), std::forward<T>(args)...); 481 if (needed < 0) { 482 buffer[0] = '\0'; 483 buffer.resize(1); 484 return; 485 } 486 if (needed < buffer.size()) { 487 buffer.resize(needed + 1); 488 return; 489 } 490 // If we're doing a heap alloc anyway might as well give it some slop 491 buffer.resize(needed + 100); 492 } 493 } 494 markDrawStart(SkCanvas & canvas)495 void RenderNode::markDrawStart(SkCanvas& canvas) { 496 StringBuffer buffer; 497 format(buffer, "RenderNode(id=%" PRId64 ", name='%s')", uniqueId(), getName()); 498 canvas.drawAnnotation(SkRect::MakeWH(getWidth(), getHeight()), buffer.data(), nullptr); 499 } 500 markDrawEnd(SkCanvas & canvas)501 void RenderNode::markDrawEnd(SkCanvas& canvas) { 502 StringBuffer buffer; 503 format(buffer, "/RenderNode(id=%" PRId64 ", name='%s')", uniqueId(), getName()); 504 canvas.drawAnnotation(SkRect::MakeWH(getWidth(), getHeight()), buffer.data(), nullptr); 505 } 506 507 } /* namespace uirenderer */ 508 } /* namespace android */ 509