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 "DamageAccumulator.h" 18 19 #include <log/log.h> 20 21 #include "RenderNode.h" 22 #include "utils/MathUtils.h" 23 24 namespace android { 25 namespace uirenderer { 26 27 enum TransformType { 28 TransformInvalid = 0, 29 TransformRenderNode, 30 TransformMatrix4, 31 TransformNone, 32 }; 33 34 struct DirtyStack { 35 TransformType type; 36 union { 37 const RenderNode* renderNode; 38 const Matrix4* matrix4; 39 }; 40 // When this frame is pop'd, this rect is mapped through the above transform 41 // and applied to the previous (aka parent) frame 42 SkRect pendingDirty; 43 DirtyStack* prev; 44 DirtyStack* next; 45 }; 46 47 DamageAccumulator::DamageAccumulator() { 48 mHead = mAllocator.create_trivial<DirtyStack>(); 49 memset(mHead, 0, sizeof(DirtyStack)); 50 // Create a root that we will not pop off 51 mHead->prev = mHead; 52 mHead->type = TransformNone; 53 } 54 55 static void computeTransformImpl(const DirtyStack* currentFrame, Matrix4* outMatrix) { 56 if (currentFrame->prev != currentFrame) { 57 computeTransformImpl(currentFrame->prev, outMatrix); 58 } 59 switch (currentFrame->type) { 60 case TransformRenderNode: 61 currentFrame->renderNode->applyViewPropertyTransforms(*outMatrix); 62 break; 63 case TransformMatrix4: 64 outMatrix->multiply(*currentFrame->matrix4); 65 break; 66 case TransformNone: 67 // nothing to be done 68 break; 69 default: 70 LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", currentFrame->type); 71 } 72 } 73 74 void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const { 75 outMatrix->loadIdentity(); 76 computeTransformImpl(mHead, outMatrix); 77 } 78 79 void DamageAccumulator::pushCommon() { 80 if (!mHead->next) { 81 DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>(); 82 nextFrame->next = nullptr; 83 nextFrame->prev = mHead; 84 mHead->next = nextFrame; 85 } 86 mHead = mHead->next; 87 mHead->pendingDirty.setEmpty(); 88 } 89 90 void DamageAccumulator::pushTransform(const RenderNode* transform) { 91 pushCommon(); 92 mHead->type = TransformRenderNode; 93 mHead->renderNode = transform; 94 } 95 96 void DamageAccumulator::pushTransform(const Matrix4* transform) { 97 pushCommon(); 98 mHead->type = TransformMatrix4; 99 mHead->matrix4 = transform; 100 } 101 102 void DamageAccumulator::popTransform() { 103 LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!"); 104 DirtyStack* dirtyFrame = mHead; 105 mHead = mHead->prev; 106 switch (dirtyFrame->type) { 107 case TransformRenderNode: 108 applyRenderNodeTransform(dirtyFrame); 109 break; 110 case TransformMatrix4: 111 applyMatrix4Transform(dirtyFrame); 112 break; 113 case TransformNone: 114 mHead->pendingDirty.join(dirtyFrame->pendingDirty); 115 break; 116 default: 117 LOG_ALWAYS_FATAL("Tried to pop an invalid type: %d", dirtyFrame->type); 118 } 119 } 120 121 static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) { 122 if (in.isEmpty()) return; 123 Rect temp(in); 124 if (CC_LIKELY(!matrix->isPerspective())) { 125 matrix->mapRect(temp); 126 } else { 127 // Don't attempt to calculate damage for a perspective transform 128 // as the numbers this works with can break the perspective 129 // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX 130 temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); 131 } 132 out->join(RECT_ARGS(temp)); 133 } 134 135 void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) { 136 mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty); 137 } 138 139 static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) { 140 if (in.isEmpty()) return; 141 const SkMatrix* transform = props.getTransformMatrix(); 142 SkRect temp(in); 143 if (transform && !transform->isIdentity()) { 144 if (CC_LIKELY(!transform->hasPerspective())) { 145 transform->mapRect(&temp); 146 } else { 147 // Don't attempt to calculate damage for a perspective transform 148 // as the numbers this works with can break the perspective 149 // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX 150 temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); 151 } 152 } 153 temp.offset(props.getLeft(), props.getTop()); 154 out->join(temp); 155 } 156 157 static DirtyStack* findParentRenderNode(DirtyStack* frame) { 158 while (frame->prev != frame) { 159 frame = frame->prev; 160 if (frame->type == TransformRenderNode) { 161 return frame; 162 } 163 } 164 return nullptr; 165 } 166 167 static DirtyStack* findProjectionReceiver(DirtyStack* frame) { 168 if (frame) { 169 while (frame->prev != frame) { 170 frame = frame->prev; 171 if (frame->type == TransformRenderNode 172 && frame->renderNode->hasProjectionReceiver()) { 173 return frame; 174 } 175 } 176 } 177 return nullptr; 178 } 179 180 static void applyTransforms(DirtyStack* frame, DirtyStack* end) { 181 SkRect* rect = &frame->pendingDirty; 182 while (frame != end) { 183 if (frame->type == TransformRenderNode) { 184 mapRect(frame->renderNode->properties(), *rect, rect); 185 } else { 186 mapRect(frame->matrix4, *rect, rect); 187 } 188 frame = frame->prev; 189 } 190 } 191 192 void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) { 193 if (frame->pendingDirty.isEmpty()) { 194 return; 195 } 196 197 const RenderProperties& props = frame->renderNode->properties(); 198 if (props.getAlpha() <= 0) { 199 return; 200 } 201 202 // Perform clipping 203 if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) { 204 if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) { 205 frame->pendingDirty.setEmpty(); 206 } 207 } 208 209 // apply all transforms 210 mapRect(props, frame->pendingDirty, &mHead->pendingDirty); 211 212 // project backwards if necessary 213 if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) { 214 // First, find our parent RenderNode: 215 DirtyStack* parentNode = findParentRenderNode(frame); 216 // Find our parent's projection receiver, which is what we project onto 217 DirtyStack* projectionReceiver = findProjectionReceiver(parentNode); 218 if (projectionReceiver) { 219 applyTransforms(frame, projectionReceiver); 220 projectionReceiver->pendingDirty.join(frame->pendingDirty); 221 } 222 223 frame->pendingDirty.setEmpty(); 224 } 225 } 226 227 void DamageAccumulator::dirty(float left, float top, float right, float bottom) { 228 mHead->pendingDirty.join(left, top, right, bottom); 229 } 230 231 void DamageAccumulator::peekAtDirty(SkRect* dest) const { 232 *dest = mHead->pendingDirty; 233 } 234 235 void DamageAccumulator::finish(SkRect* totalDirty) { 236 LOG_ALWAYS_FATAL_IF(mHead->prev != mHead, "Cannot finish, mismatched push/pop calls! %p vs. %p", mHead->prev, mHead); 237 // Root node never has a transform, so this is the fully mapped dirty rect 238 *totalDirty = mHead->pendingDirty; 239 totalDirty->roundOut(totalDirty); 240 mHead->pendingDirty.setEmpty(); 241 } 242 243 } /* namespace uirenderer */ 244 } /* namespace android */ 245