FrameBuilder.cpp revision c9bb1a38d356087a4e5578307a6839eac0a1e6ee
1/* 2 * Copyright (C) 2016 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 "FrameBuilder.h" 18 19#include "LayerUpdateQueue.h" 20#include "RenderNode.h" 21#include "VectorDrawable.h" 22#include "renderstate/OffscreenBufferPool.h" 23#include "hwui/Canvas.h" 24#include "utils/FatVector.h" 25#include "utils/PaintUtils.h" 26#include "utils/TraceUtils.h" 27 28#include <SkPathOps.h> 29#include <utils/TypeHelpers.h> 30 31namespace android { 32namespace uirenderer { 33 34FrameBuilder::FrameBuilder(const SkRect& clip, 35 uint32_t viewportWidth, uint32_t viewportHeight, 36 const LightGeometry& lightGeometry, Caches& caches) 37 : mStdAllocator(mAllocator) 38 , mLayerBuilders(mStdAllocator) 39 , mLayerStack(mStdAllocator) 40 , mCanvasState(*this) 41 , mCaches(caches) 42 , mLightRadius(lightGeometry.radius) 43 , mDrawFbo0(true) { 44 45 // Prepare to defer Fbo0 46 auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip)); 47 mLayerBuilders.push_back(fbo0); 48 mLayerStack.push_back(0); 49 mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, 50 clip.fLeft, clip.fTop, clip.fRight, clip.fBottom, 51 lightGeometry.center); 52} 53 54FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, 55 const LightGeometry& lightGeometry, Caches& caches) 56 : mStdAllocator(mAllocator) 57 , mLayerBuilders(mStdAllocator) 58 , mLayerStack(mStdAllocator) 59 , mCanvasState(*this) 60 , mCaches(caches) 61 , mLightRadius(lightGeometry.radius) 62 , mDrawFbo0(false) { 63 // TODO: remove, with each layer on its own save stack 64 65 // Prepare to defer Fbo0 (which will be empty) 66 auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1)); 67 mLayerBuilders.push_back(fbo0); 68 mLayerStack.push_back(0); 69 mCanvasState.initializeSaveStack(1, 1, 70 0, 0, 1, 1, 71 lightGeometry.center); 72 73 deferLayers(layers); 74} 75 76void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) { 77 // Render all layers to be updated, in order. Defer in reverse order, so that they'll be 78 // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse) 79 for (int i = layers.entries().size() - 1; i >= 0; i--) { 80 RenderNode* layerNode = layers.entries()[i].renderNode; 81 // only schedule repaint if node still on layer - possible it may have been 82 // removed during a dropped frame, but layers may still remain scheduled so 83 // as not to lose info on what portion is damaged 84 OffscreenBuffer* layer = layerNode->getLayer(); 85 if (CC_LIKELY(layer)) { 86 ATRACE_FORMAT("Optimize HW Layer DisplayList %s %ux%u", 87 layerNode->getName(), layerNode->getWidth(), layerNode->getHeight()); 88 89 const Rect& layerDamage = layers.entries()[i].damage; 90 layerNode->computeOrdering(); 91 92 // map current light center into RenderNode's coordinate space 93 Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter(); 94 layer->inverseTransformInWindow.mapPoint3d(lightCenter); 95 96 saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0, 97 layerDamage, lightCenter, nullptr, layerNode); 98 99 if (layerNode->getDisplayList()) { 100 deferNodeOps(*layerNode); 101 } 102 restoreForLayer(); 103 } 104 } 105} 106 107void FrameBuilder::deferRenderNode(RenderNode& renderNode) { 108 renderNode.computeOrdering(); 109 110 mCanvasState.save(SaveFlags::MatrixClip); 111 deferNodePropsAndOps(renderNode); 112 mCanvasState.restore(); 113} 114 115void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) { 116 renderNode.computeOrdering(); 117 118 mCanvasState.save(SaveFlags::MatrixClip); 119 mCanvasState.translate(tx, ty); 120 mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, 121 SkRegion::kIntersect_Op); 122 deferNodePropsAndOps(renderNode); 123 mCanvasState.restore(); 124} 125 126static Rect nodeBounds(RenderNode& node) { 127 auto& props = node.properties(); 128 return Rect(props.getLeft(), props.getTop(), 129 props.getRight(), props.getBottom()); 130} 131 132void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes, 133 const Rect& contentDrawBounds) { 134 if (nodes.size() < 1) return; 135 if (nodes.size() == 1) { 136 if (!nodes[0]->nothingToDraw()) { 137 deferRenderNode(*nodes[0]); 138 } 139 return; 140 } 141 // It there are multiple render nodes, they are laid out as follows: 142 // #0 - backdrop (content + caption) 143 // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop) 144 // #2 - additional overlay nodes 145 // Usually the backdrop cannot be seen since it will be entirely covered by the content. While 146 // resizing however it might become partially visible. The following render loop will crop the 147 // backdrop against the content and draw the remaining part of it. It will then draw the content 148 // cropped to the backdrop (since that indicates a shrinking of the window). 149 // 150 // Additional nodes will be drawn on top with no particular clipping semantics. 151 152 // Usually the contents bounds should be mContentDrawBounds - however - we will 153 // move it towards the fixed edge to give it a more stable appearance (for the moment). 154 // If there is no content bounds we ignore the layering as stated above and start with 2. 155 156 // Backdrop bounds in render target space 157 const Rect backdrop = nodeBounds(*nodes[0]); 158 159 // Bounds that content will fill in render target space (note content node bounds may be bigger) 160 Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight()); 161 content.translate(backdrop.left, backdrop.top); 162 if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) { 163 // Content doesn't entirely overlap backdrop, so fill around content (right/bottom) 164 165 // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to 166 // also fill left/top. Currently, both 2up and freeform position content at the top/left of 167 // the backdrop, so this isn't necessary. 168 if (content.right < backdrop.right) { 169 // draw backdrop to right side of content 170 deferRenderNode(0, 0, Rect(content.right, backdrop.top, 171 backdrop.right, backdrop.bottom), *nodes[0]); 172 } 173 if (content.bottom < backdrop.bottom) { 174 // draw backdrop to bottom of content 175 // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill 176 deferRenderNode(0, 0, Rect(content.left, content.bottom, 177 content.right, backdrop.bottom), *nodes[0]); 178 } 179 } 180 181 if (!backdrop.isEmpty()) { 182 // content node translation to catch up with backdrop 183 float dx = contentDrawBounds.left - backdrop.left; 184 float dy = contentDrawBounds.top - backdrop.top; 185 186 Rect contentLocalClip = backdrop; 187 contentLocalClip.translate(dx, dy); 188 deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]); 189 } else { 190 deferRenderNode(*nodes[1]); 191 } 192 193 // remaining overlay nodes, simply defer 194 for (size_t index = 2; index < nodes.size(); index++) { 195 if (!nodes[index]->nothingToDraw()) { 196 deferRenderNode(*nodes[index]); 197 } 198 } 199} 200 201void FrameBuilder::onViewportInitialized() {} 202 203void FrameBuilder::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} 204 205void FrameBuilder::deferNodePropsAndOps(RenderNode& node) { 206 const RenderProperties& properties = node.properties(); 207 const Outline& outline = properties.getOutline(); 208 if (properties.getAlpha() <= 0 209 || (outline.getShouldClip() && outline.isEmpty()) 210 || properties.getScaleX() == 0 211 || properties.getScaleY() == 0) { 212 return; // rejected 213 } 214 215 if (properties.getLeft() != 0 || properties.getTop() != 0) { 216 mCanvasState.translate(properties.getLeft(), properties.getTop()); 217 } 218 if (properties.getStaticMatrix()) { 219 mCanvasState.concatMatrix(*properties.getStaticMatrix()); 220 } else if (properties.getAnimationMatrix()) { 221 mCanvasState.concatMatrix(*properties.getAnimationMatrix()); 222 } 223 if (properties.hasTransformMatrix()) { 224 if (properties.isTransformTranslateOnly()) { 225 mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY()); 226 } else { 227 mCanvasState.concatMatrix(*properties.getTransformMatrix()); 228 } 229 } 230 231 const int width = properties.getWidth(); 232 const int height = properties.getHeight(); 233 234 Rect saveLayerBounds; // will be set to non-empty if saveLayer needed 235 const bool isLayer = properties.effectiveLayerType() != LayerType::None; 236 int clipFlags = properties.getClippingFlags(); 237 if (properties.getAlpha() < 1) { 238 if (isLayer) { 239 clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer 240 } 241 if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) { 242 // simply scale rendering content's alpha 243 mCanvasState.scaleAlpha(properties.getAlpha()); 244 } else { 245 // schedule saveLayer by initializing saveLayerBounds 246 saveLayerBounds.set(0, 0, width, height); 247 if (clipFlags) { 248 properties.getClippingRectForFlags(clipFlags, &saveLayerBounds); 249 clipFlags = 0; // all clipping done by savelayer 250 } 251 } 252 253 if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) { 254 // pretend alpha always causes savelayer to warn about 255 // performance problem affecting old versions 256 ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height); 257 } 258 } 259 if (clipFlags) { 260 Rect clipRect; 261 properties.getClippingRectForFlags(clipFlags, &clipRect); 262 mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, 263 SkRegion::kIntersect_Op); 264 } 265 266 if (properties.getRevealClip().willClip()) { 267 Rect bounds; 268 properties.getRevealClip().getBounds(&bounds); 269 mCanvasState.setClippingRoundRect(mAllocator, 270 bounds, properties.getRevealClip().getRadius()); 271 } else if (properties.getOutline().willClip()) { 272 mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline())); 273 } 274 275 bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty() 276 || (properties.getClipToBounds() 277 && mCanvasState.quickRejectConservative(0, 0, width, height)); 278 if (!quickRejected) { 279 // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer) 280 if (node.getLayer()) { 281 // HW layer 282 LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node); 283 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); 284 if (bakedOpState) { 285 // Node's layer already deferred, schedule it to render into parent layer 286 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); 287 } 288 } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) { 289 // draw DisplayList contents within temporary, since persisted layer could not be used. 290 // (temp layers are clipped to viewport, since they don't persist offscreen content) 291 SkPaint saveLayerPaint; 292 saveLayerPaint.setAlpha(properties.getAlpha()); 293 deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>( 294 saveLayerBounds, 295 Matrix4::identity(), 296 nullptr, // no record-time clip - need only respect defer-time one 297 &saveLayerPaint)); 298 deferNodeOps(node); 299 deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>()); 300 } else { 301 deferNodeOps(node); 302 } 303 } 304} 305 306typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair; 307 308template <typename V> 309static void buildZSortedChildList(V* zTranslatedNodes, 310 const DisplayList& displayList, const DisplayList::Chunk& chunk) { 311 if (chunk.beginChildIndex == chunk.endChildIndex) return; 312 313 for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) { 314 RenderNodeOp* childOp = displayList.getChildren()[i]; 315 RenderNode* child = childOp->renderNode; 316 float childZ = child->properties().getZ(); 317 318 if (!MathUtils::isZero(childZ) && chunk.reorderChildren) { 319 zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp)); 320 childOp->skipInOrderDraw = true; 321 } else if (!child->properties().getProjectBackwards()) { 322 // regular, in order drawing DisplayList 323 childOp->skipInOrderDraw = false; 324 } 325 } 326 327 // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order) 328 std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end()); 329} 330 331template <typename V> 332static size_t findNonNegativeIndex(const V& zTranslatedNodes) { 333 for (size_t i = 0; i < zTranslatedNodes.size(); i++) { 334 if (zTranslatedNodes[i].key >= 0.0f) return i; 335 } 336 return zTranslatedNodes.size(); 337} 338 339template <typename V> 340void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode, 341 const V& zTranslatedNodes) { 342 const int size = zTranslatedNodes.size(); 343 if (size == 0 344 || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f) 345 || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) { 346 // no 3d children to draw 347 return; 348 } 349 350 /** 351 * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters 352 * with very similar Z heights to draw together. 353 * 354 * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are 355 * underneath both, and neither's shadow is drawn on top of the other. 356 */ 357 const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes); 358 size_t drawIndex, shadowIndex, endIndex; 359 if (mode == ChildrenSelectMode::Negative) { 360 drawIndex = 0; 361 endIndex = nonNegativeIndex; 362 shadowIndex = endIndex; // draw no shadows 363 } else { 364 drawIndex = nonNegativeIndex; 365 endIndex = size; 366 shadowIndex = drawIndex; // potentially draw shadow for each pos Z child 367 } 368 369 float lastCasterZ = 0.0f; 370 while (shadowIndex < endIndex || drawIndex < endIndex) { 371 if (shadowIndex < endIndex) { 372 const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value; 373 const float casterZ = zTranslatedNodes[shadowIndex].key; 374 // attempt to render the shadow if the caster about to be drawn is its caster, 375 // OR if its caster's Z value is similar to the previous potential caster 376 if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) { 377 deferShadow(reorderClip, *casterNodeOp); 378 379 lastCasterZ = casterZ; // must do this even if current caster not casting a shadow 380 shadowIndex++; 381 continue; 382 } 383 } 384 385 const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value; 386 deferRenderNodeOpImpl(*childOp); 387 drawIndex++; 388 } 389} 390 391void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) { 392 auto& node = *casterNodeOp.renderNode; 393 auto& properties = node.properties(); 394 395 if (properties.getAlpha() <= 0.0f 396 || properties.getOutline().getAlpha() <= 0.0f 397 || !properties.getOutline().getPath() 398 || properties.getScaleX() == 0 399 || properties.getScaleY() == 0) { 400 // no shadow to draw 401 return; 402 } 403 404 const SkPath* casterOutlinePath = properties.getOutline().getPath(); 405 const SkPath* revealClipPath = properties.getRevealClip().getPath(); 406 if (revealClipPath && revealClipPath->isEmpty()) return; 407 408 float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha(); 409 410 // holds temporary SkPath to store the result of intersections 411 SkPath* frameAllocatedPath = nullptr; 412 const SkPath* casterPath = casterOutlinePath; 413 414 // intersect the shadow-casting path with the reveal, if present 415 if (revealClipPath) { 416 frameAllocatedPath = createFrameAllocatedPath(); 417 418 Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath); 419 casterPath = frameAllocatedPath; 420 } 421 422 // intersect the shadow-casting path with the clipBounds, if present 423 if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) { 424 if (!frameAllocatedPath) { 425 frameAllocatedPath = createFrameAllocatedPath(); 426 } 427 Rect clipBounds; 428 properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds); 429 SkPath clipBoundsPath; 430 clipBoundsPath.addRect(clipBounds.left, clipBounds.top, 431 clipBounds.right, clipBounds.bottom); 432 433 Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath); 434 casterPath = frameAllocatedPath; 435 } 436 437 // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk 438 int restoreTo = mCanvasState.save(SaveFlags::MatrixClip); 439 mCanvasState.writableSnapshot()->applyClip(reorderClip, 440 *mCanvasState.currentSnapshot()->transform); 441 if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) { 442 Matrix4 shadowMatrixXY(casterNodeOp.localMatrix); 443 Matrix4 shadowMatrixZ(casterNodeOp.localMatrix); 444 node.applyViewPropertyTransforms(shadowMatrixXY, false); 445 node.applyViewPropertyTransforms(shadowMatrixZ, true); 446 447 sp<TessellationCache::ShadowTask> task = mCaches.tessellationCache.getShadowTask( 448 mCanvasState.currentTransform(), 449 mCanvasState.getLocalClipBounds(), 450 casterAlpha >= 1.0f, 451 casterPath, 452 &shadowMatrixXY, &shadowMatrixZ, 453 mCanvasState.currentSnapshot()->getRelativeLightCenter(), 454 mLightRadius); 455 ShadowOp* shadowOp = mAllocator.create<ShadowOp>(task, casterAlpha); 456 BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct( 457 mAllocator, *mCanvasState.writableSnapshot(), shadowOp); 458 if (CC_LIKELY(bakedOpState)) { 459 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow); 460 } 461 } 462 mCanvasState.restoreToCount(restoreTo); 463} 464 465void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) { 466 int count = mCanvasState.save(SaveFlags::MatrixClip); 467 const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath(); 468 469 SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy 470 if (projectionReceiverOutline) { 471 // transform the mask for this projector into render target space 472 // TODO: consider combining both transforms by stashing transform instead of applying 473 SkMatrix skCurrentTransform; 474 mCanvasState.currentTransform()->copyTo(skCurrentTransform); 475 projectionReceiverOutline->transform( 476 skCurrentTransform, 477 &transformedMaskPath); 478 mCanvasState.setProjectionPathMask(mAllocator, &transformedMaskPath); 479 } 480 481 for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) { 482 RenderNodeOp* childOp = renderNode.mProjectedNodes[i]; 483 RenderNode& childNode = *childOp->renderNode; 484 485 // Draw child if it has content, but ignore state in childOp - matrix already applied to 486 // transformFromCompositingAncestor, and record-time clip is ignored when projecting 487 if (!childNode.nothingToDraw()) { 488 int restoreTo = mCanvasState.save(SaveFlags::MatrixClip); 489 490 // Apply transform between ancestor and projected descendant 491 mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor); 492 493 deferNodePropsAndOps(childNode); 494 495 mCanvasState.restoreToCount(restoreTo); 496 } 497 } 498 mCanvasState.restoreToCount(count); 499} 500 501/** 502 * Used to define a list of lambdas referencing private FrameBuilder::onXX::defer() methods. 503 * 504 * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. 505 * E.g. a BitmapOp op then would be dispatched to FrameBuilder::onBitmapOp(const BitmapOp&) 506 */ 507#define OP_RECEIVER(Type) \ 508 [](FrameBuilder& frameBuilder, const RecordedOp& op) { frameBuilder.defer##Type(static_cast<const Type&>(op)); }, 509void FrameBuilder::deferNodeOps(const RenderNode& renderNode) { 510 typedef void (*OpDispatcher) (FrameBuilder& frameBuilder, const RecordedOp& op); 511 static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER); 512 513 // can't be null, since DL=null node rejection happens before deferNodePropsAndOps 514 const DisplayList& displayList = *(renderNode.getDisplayList()); 515 for (auto& chunk : displayList.getChunks()) { 516 FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes; 517 buildZSortedChildList(&zTranslatedNodes, displayList, chunk); 518 519 defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes); 520 for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { 521 const RecordedOp* op = displayList.getOps()[opIndex]; 522 receivers[op->opId](*this, *op); 523 524 if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty() 525 && displayList.projectionReceiveIndex >= 0 526 && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) { 527 deferProjectedChildren(renderNode); 528 } 529 } 530 defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes); 531 } 532} 533 534void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) { 535 if (op.renderNode->nothingToDraw()) return; 536 int count = mCanvasState.save(SaveFlags::MatrixClip); 537 538 // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix) 539 mCanvasState.writableSnapshot()->applyClip(op.localClip, 540 *mCanvasState.currentSnapshot()->transform); 541 mCanvasState.concatMatrix(op.localMatrix); 542 543 // then apply state from node properties, and defer ops 544 deferNodePropsAndOps(*op.renderNode); 545 546 mCanvasState.restoreToCount(count); 547} 548 549void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) { 550 if (!op.skipInOrderDraw) { 551 deferRenderNodeOpImpl(op); 552 } 553} 554 555/** 556 * Defers an unmergeable, strokeable op, accounting correctly 557 * for paint's style on the bounds being computed. 558 */ 559BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, 560 BakedOpState::StrokeBehavior strokeBehavior) { 561 // Note: here we account for stroke when baking the op 562 BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( 563 mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior); 564 if (!bakedState) return nullptr; // quick rejected 565 566 if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) { 567 bakedState->setupOpacity(op.paint); 568 } 569 570 currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); 571 return bakedState; 572} 573 574/** 575 * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will 576 * be used, since they trigger significantly different rendering paths. 577 * 578 * Note: not used for lines/points, since they don't currently support path effects. 579 */ 580static batchid_t tessBatchId(const RecordedOp& op) { 581 const SkPaint& paint = *(op.paint); 582 return paint.getPathEffect() 583 ? OpBatchType::AlphaMaskTexture 584 : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices); 585} 586 587void FrameBuilder::deferArcOp(const ArcOp& op) { 588 deferStrokeableOp(op, tessBatchId(op)); 589} 590 591static bool hasMergeableClip(const BakedOpState& state) { 592 return state.computedState.clipState 593 || state.computedState.clipState->mode == ClipMode::Rectangle; 594} 595 596void FrameBuilder::deferBitmapOp(const BitmapOp& op) { 597 BakedOpState* bakedState = tryBakeOpState(op); 598 if (!bakedState) return; // quick rejected 599 600 if (op.bitmap->isOpaque()) { 601 bakedState->setupOpacity(op.paint); 602 } 603 604 // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation 605 // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in 606 // MergingDrawBatch::canMergeWith() 607 if (bakedState->computedState.transform.isSimple() 608 && bakedState->computedState.transform.positiveScale() 609 && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode 610 && op.bitmap->colorType() != kAlpha_8_SkColorType 611 && hasMergeableClip(*bakedState)) { 612 mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); 613 // TODO: AssetAtlas in mergeId 614 currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId); 615 } else { 616 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); 617 } 618} 619 620void FrameBuilder::deferBitmapMeshOp(const BitmapMeshOp& op) { 621 BakedOpState* bakedState = tryBakeOpState(op); 622 if (!bakedState) return; // quick rejected 623 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); 624} 625 626void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) { 627 BakedOpState* bakedState = tryBakeOpState(op); 628 if (!bakedState) return; // quick rejected 629 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); 630} 631 632void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) { 633 const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty(); 634 SkPaint* paint = op.vectorDrawable->getPaint(); 635 const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds, 636 op.localMatrix, 637 op.localClip, 638 paint, 639 &bitmap, 640 Rect(bitmap.width(), bitmap.height())); 641 deferBitmapRectOp(*resolvedOp); 642} 643 644void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) { 645 // allocate a temporary oval op (with mAllocator, so it persists until render), so the 646 // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. 647 float x = *(op.x); 648 float y = *(op.y); 649 float radius = *(op.radius); 650 Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius); 651 const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>( 652 unmappedBounds, 653 op.localMatrix, 654 op.localClip, 655 op.paint); 656 deferOvalOp(*resolvedOp); 657} 658 659void FrameBuilder::deferColorOp(const ColorOp& op) { 660 BakedOpState* bakedState = tryBakeUnboundedOpState(op); 661 if (!bakedState) return; // quick rejected 662 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); 663} 664 665void FrameBuilder::deferFunctorOp(const FunctorOp& op) { 666 BakedOpState* bakedState = tryBakeUnboundedOpState(op); 667 if (!bakedState) return; // quick rejected 668 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor); 669} 670 671void FrameBuilder::deferLinesOp(const LinesOp& op) { 672 batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; 673 deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); 674} 675 676void FrameBuilder::deferOvalOp(const OvalOp& op) { 677 deferStrokeableOp(op, tessBatchId(op)); 678} 679 680void FrameBuilder::deferPatchOp(const PatchOp& op) { 681 BakedOpState* bakedState = tryBakeOpState(op); 682 if (!bakedState) return; // quick rejected 683 684 if (bakedState->computedState.transform.isPureTranslate() 685 && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode 686 && hasMergeableClip(*bakedState)) { 687 mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); 688 // TODO: AssetAtlas in mergeId 689 690 // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together 691 currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId); 692 } else { 693 // Use Bitmap batchId since Bitmap+Patch use same shader 694 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); 695 } 696} 697 698void FrameBuilder::deferPathOp(const PathOp& op) { 699 auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture); 700 if (CC_LIKELY(state)) { 701 mCaches.pathCache.precache(op.path, op.paint); 702 } 703} 704 705void FrameBuilder::deferPointsOp(const PointsOp& op) { 706 batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; 707 deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); 708} 709 710void FrameBuilder::deferRectOp(const RectOp& op) { 711 deferStrokeableOp(op, tessBatchId(op)); 712} 713 714void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) { 715 auto state = deferStrokeableOp(op, tessBatchId(op)); 716 if (CC_LIKELY(state && !op.paint->getPathEffect())) { 717 // TODO: consider storing tessellation task in BakedOpState 718 mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint), 719 op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry); 720 } 721} 722 723void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) { 724 // allocate a temporary round rect op (with mAllocator, so it persists until render), so the 725 // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. 726 const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>( 727 Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)), 728 op.localMatrix, 729 op.localClip, 730 op.paint, *op.rx, *op.ry); 731 deferRoundRectOp(*resolvedOp); 732} 733 734void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) { 735 BakedOpState* bakedState = tryBakeOpState(op); 736 if (!bakedState) return; // quick rejected 737 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); 738} 739 740static batchid_t textBatchId(const SkPaint& paint) { 741 // TODO: better handling of shader (since we won't care about color then) 742 return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText; 743} 744 745void FrameBuilder::deferTextOp(const TextOp& op) { 746 BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( 747 mAllocator, *mCanvasState.writableSnapshot(), op, 748 BakedOpState::StrokeBehavior::StyleDefined); 749 if (!bakedState) return; // quick rejected 750 751 batchid_t batchId = textBatchId(*(op.paint)); 752 if (bakedState->computedState.transform.isPureTranslate() 753 && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode 754 && hasMergeableClip(*bakedState)) { 755 mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor()); 756 currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId); 757 } else { 758 currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); 759 } 760 761 FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); 762 auto& totalTransform = bakedState->computedState.transform; 763 if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) { 764 fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I()); 765 } else { 766 // Partial transform case, see BakedOpDispatcher::renderTextOp 767 float sx, sy; 768 totalTransform.decomposeScale(sx, sy); 769 fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::MakeScale( 770 roundf(std::max(1.0f, sx)), 771 roundf(std::max(1.0f, sy)))); 772 } 773} 774 775void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) { 776 BakedOpState* bakedState = tryBakeUnboundedOpState(op); 777 if (!bakedState) return; // quick rejected 778 currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint))); 779 780 mCaches.fontRenderer.getFontRenderer().precache( 781 op.paint, op.glyphs, op.glyphCount, SkMatrix::I()); 782} 783 784void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) { 785 if (CC_UNLIKELY(!op.layer->isRenderable())) return; 786 787 const TextureLayerOp* textureLayerOp = &op; 788 // Now safe to access transform (which was potentially unready at record time) 789 if (!op.layer->getTransform().isIdentity()) { 790 // non-identity transform present, so 'inject it' into op by copying + replacing matrix 791 Matrix4 combinedMatrix(op.localMatrix); 792 combinedMatrix.multiply(op.layer->getTransform()); 793 textureLayerOp = mAllocator.create<TextureLayerOp>(op, combinedMatrix); 794 } 795 BakedOpState* bakedState = tryBakeOpState(*textureLayerOp); 796 797 if (!bakedState) return; // quick rejected 798 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer); 799} 800 801void FrameBuilder::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, 802 float contentTranslateX, float contentTranslateY, 803 const Rect& repaintRect, 804 const Vector3& lightCenter, 805 const BeginLayerOp* beginLayerOp, RenderNode* renderNode) { 806 mCanvasState.save(SaveFlags::MatrixClip); 807 mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); 808 mCanvasState.writableSnapshot()->roundRectClipState = nullptr; 809 mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter); 810 mCanvasState.writableSnapshot()->transform->loadTranslate( 811 contentTranslateX, contentTranslateY, 0); 812 mCanvasState.writableSnapshot()->setClip( 813 repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom); 814 815 // create a new layer repaint, and push its index on the stack 816 mLayerStack.push_back(mLayerBuilders.size()); 817 auto newFbo = mAllocator.create<LayerBuilder>(layerWidth, layerHeight, 818 repaintRect, beginLayerOp, renderNode); 819 mLayerBuilders.push_back(newFbo); 820} 821 822void FrameBuilder::restoreForLayer() { 823 // restore canvas, and pop finished layer off of the stack 824 mCanvasState.restore(); 825 mLayerStack.pop_back(); 826} 827 828// TODO: defer time rejection (when bounds become empty) + tests 829// Option - just skip layers with no bounds at playback + defer? 830void FrameBuilder::deferBeginLayerOp(const BeginLayerOp& op) { 831 uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); 832 uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); 833 834 auto previous = mCanvasState.currentSnapshot(); 835 Vector3 lightCenter = previous->getRelativeLightCenter(); 836 837 // Combine all transforms used to present saveLayer content: 838 // parent content transform * canvas transform * bounds offset 839 Matrix4 contentTransform(*(previous->transform)); 840 contentTransform.multiply(op.localMatrix); 841 contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top); 842 843 Matrix4 inverseContentTransform; 844 inverseContentTransform.loadInverse(contentTransform); 845 846 // map the light center into layer-relative space 847 inverseContentTransform.mapPoint3d(lightCenter); 848 849 // Clip bounds of temporary layer to parent's clip rect, so: 850 Rect saveLayerBounds(layerWidth, layerHeight); 851 // 1) transform Rect(width, height) into parent's space 852 // note: left/top offsets put in contentTransform above 853 contentTransform.mapRect(saveLayerBounds); 854 // 2) intersect with parent's clip 855 saveLayerBounds.doIntersect(previous->getRenderTargetClip()); 856 // 3) and transform back 857 inverseContentTransform.mapRect(saveLayerBounds); 858 saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight)); 859 saveLayerBounds.roundOut(); 860 861 // if bounds are reduced, will clip the layer's area by reducing required bounds... 862 layerWidth = saveLayerBounds.getWidth(); 863 layerHeight = saveLayerBounds.getHeight(); 864 // ...and shifting drawing content to account for left/top side clipping 865 float contentTranslateX = -saveLayerBounds.left; 866 float contentTranslateY = -saveLayerBounds.top; 867 868 saveForLayer(layerWidth, layerHeight, 869 contentTranslateX, contentTranslateY, 870 Rect(layerWidth, layerHeight), 871 lightCenter, 872 &op, nullptr); 873} 874 875void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) { 876 const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; 877 int finishedLayerIndex = mLayerStack.back(); 878 879 restoreForLayer(); 880 881 // saveLayer will clip & translate the draw contents, so we need 882 // to translate the drawLayer by how much the contents was translated 883 // TODO: Unify this with beginLayerOp so we don't have to calculate this 884 // twice 885 uint32_t layerWidth = (uint32_t) beginLayerOp.unmappedBounds.getWidth(); 886 uint32_t layerHeight = (uint32_t) beginLayerOp.unmappedBounds.getHeight(); 887 888 auto previous = mCanvasState.currentSnapshot(); 889 Vector3 lightCenter = previous->getRelativeLightCenter(); 890 891 // Combine all transforms used to present saveLayer content: 892 // parent content transform * canvas transform * bounds offset 893 Matrix4 contentTransform(*(previous->transform)); 894 contentTransform.multiply(beginLayerOp.localMatrix); 895 contentTransform.translate(beginLayerOp.unmappedBounds.left, 896 beginLayerOp.unmappedBounds.top); 897 898 Matrix4 inverseContentTransform; 899 inverseContentTransform.loadInverse(contentTransform); 900 901 // map the light center into layer-relative space 902 inverseContentTransform.mapPoint3d(lightCenter); 903 904 // Clip bounds of temporary layer to parent's clip rect, so: 905 Rect saveLayerBounds(layerWidth, layerHeight); 906 // 1) transform Rect(width, height) into parent's space 907 // note: left/top offsets put in contentTransform above 908 contentTransform.mapRect(saveLayerBounds); 909 // 2) intersect with parent's clip 910 saveLayerBounds.doIntersect(previous->getRenderTargetClip()); 911 // 3) and transform back 912 inverseContentTransform.mapRect(saveLayerBounds); 913 saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight)); 914 saveLayerBounds.roundOut(); 915 916 Matrix4 localMatrix(beginLayerOp.localMatrix); 917 localMatrix.translate(saveLayerBounds.left, saveLayerBounds.top); 918 919 // record the draw operation into the previous layer's list of draw commands 920 // uses state from the associated beginLayerOp, since it has all the state needed for drawing 921 LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>( 922 beginLayerOp.unmappedBounds, 923 localMatrix, 924 beginLayerOp.localClip, 925 beginLayerOp.paint, 926 &(mLayerBuilders[finishedLayerIndex]->offscreenBuffer)); 927 BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); 928 929 if (bakedOpState) { 930 // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack) 931 currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); 932 } else { 933 // Layer won't be drawn - delete its drawing batches to prevent it from doing any work 934 // TODO: need to prevent any render work from being done 935 // - create layerop earlier for reject purposes? 936 mLayerBuilders[finishedLayerIndex]->clear(); 937 return; 938 } 939} 940 941void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { 942 Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform)); 943 boundsTransform.multiply(op.localMatrix); 944 945 Rect dstRect(op.unmappedBounds); 946 boundsTransform.mapRect(dstRect); 947 dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip()); 948 949 if (dstRect.isEmpty()) { 950 // Unclipped layer rejected - push a null op, so next EndUnclippedLayerOp is ignored 951 currentLayer().activeUnclippedSaveLayers.push_back(nullptr); 952 } else { 953 // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume) 954 OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>(nullptr); 955 956 /** 957 * First, defer an operation to copy out the content from the rendertarget into a layer. 958 */ 959 auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle); 960 BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator, 961 &(currentLayer().repaintClip), dstRect, *copyToOp); 962 currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer); 963 964 /** 965 * Defer a clear rect, so that clears from multiple unclipped layers can be drawn 966 * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible 967 */ 968 currentLayer().deferLayerClear(dstRect); 969 970 /** 971 * And stash an operation to copy that layer back under the rendertarget until 972 * a balanced EndUnclippedLayerOp is seen 973 */ 974 auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle); 975 bakedState = BakedOpState::directConstruct(mAllocator, 976 &(currentLayer().repaintClip), dstRect, *copyFromOp); 977 currentLayer().activeUnclippedSaveLayers.push_back(bakedState); 978 } 979} 980 981void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) { 982 LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!"); 983 984 BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back(); 985 currentLayer().activeUnclippedSaveLayers.pop_back(); 986 if (copyFromLayerOp) { 987 currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer); 988 } 989} 990 991void FrameBuilder::finishDefer() { 992 mCaches.fontRenderer.endPrecaching(); 993} 994 995} // namespace uirenderer 996} // namespace android 997