/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DamageAccumulator.h"
#include <cutils/log.h>
#include "RenderNode.h"
#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
enum TransformType {
TransformInvalid = 0,
TransformRenderNode,
TransformMatrix4,
TransformNone,
};
struct DirtyStack {
TransformType type;
union {
const RenderNode* renderNode;
const Matrix4* matrix4;
};
// When this frame is pop'd, this rect is mapped through the above transform
// and applied to the previous (aka parent) frame
SkRect pendingDirty;
DirtyStack* prev;
DirtyStack* next;
};
DamageAccumulator::DamageAccumulator() {
mHead = mAllocator.create_trivial<DirtyStack>();
memset(mHead, 0, sizeof(DirtyStack));
// Create a root that we will not pop off
mHead->prev = mHead;
mHead->type = TransformNone;
}
static void computeTransformImpl(const DirtyStack* currentFrame, Matrix4* outMatrix) {
if (currentFrame->prev != currentFrame) {
computeTransformImpl(currentFrame->prev, outMatrix);
}
switch (currentFrame->type) {
case TransformRenderNode:
currentFrame->renderNode->applyViewPropertyTransforms(*outMatrix);
break;
case TransformMatrix4:
outMatrix->multiply(*currentFrame->matrix4);
break;
case TransformNone:
// nothing to be done
break;
default:
LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", currentFrame->type);
}
}
void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const {
outMatrix->loadIdentity();
computeTransformImpl(mHead, outMatrix);
}
void DamageAccumulator::pushCommon() {
if (!mHead->next) {
DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>();
nextFrame->next = nullptr;
nextFrame->prev = mHead;
mHead->next = nextFrame;
}
mHead = mHead->next;
mHead->pendingDirty.setEmpty();
}
void DamageAccumulator::pushTransform(const RenderNode* transform) {
pushCommon();
mHead->type = TransformRenderNode;
mHead->renderNode = transform;
}
void DamageAccumulator::pushTransform(const Matrix4* transform) {
pushCommon();
mHead->type = TransformMatrix4;
mHead->matrix4 = transform;
}
void DamageAccumulator::popTransform() {
LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
DirtyStack* dirtyFrame = mHead;
mHead = mHead->prev;
switch (dirtyFrame->type) {
case TransformRenderNode:
applyRenderNodeTransform(dirtyFrame);
break;
case TransformMatrix4:
applyMatrix4Transform(dirtyFrame);
break;
case TransformNone:
mHead->pendingDirty.join(dirtyFrame->pendingDirty);
break;
default:
LOG_ALWAYS_FATAL("Tried to pop an invalid type: %d", dirtyFrame->type);
}
}
static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
if (in.isEmpty()) return;
Rect temp(in);
if (CC_LIKELY(!matrix->isPerspective())) {
matrix->mapRect(temp);
} else {
// Don't attempt to calculate damage for a perspective transform
// as the numbers this works with can break the perspective
// calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
}
out->join(RECT_ARGS(temp));
}
void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
}
static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
if (in.isEmpty()) return;
const SkMatrix* transform = props.getTransformMatrix();
SkRect temp(in);
if (transform && !transform->isIdentity()) {
if (CC_LIKELY(!transform->hasPerspective())) {
transform->mapRect(&temp);
} else {
// Don't attempt to calculate damage for a perspective transform
// as the numbers this works with can break the perspective
// calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
}
}
temp.offset(props.getLeft(), props.getTop());
out->join(temp);
}
static DirtyStack* findParentRenderNode(DirtyStack* frame) {
while (frame->prev != frame) {
frame = frame->prev;
if (frame->type == TransformRenderNode) {
return frame;
}
}
return nullptr;
}
static DirtyStack* findProjectionReceiver(DirtyStack* frame) {
if (frame) {
while (frame->prev != frame) {
frame = frame->prev;
if (frame->type == TransformRenderNode
&& frame->renderNode->hasProjectionReceiver()) {
return frame;
}
}
}
return nullptr;
}
static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
SkRect* rect = &frame->pendingDirty;
while (frame != end) {
if (frame->type == TransformRenderNode) {
mapRect(frame->renderNode->properties(), *rect, rect);
} else {
mapRect(frame->matrix4, *rect, rect);
}
frame = frame->prev;
}
}
void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
if (frame->pendingDirty.isEmpty()) {
return;
}
const RenderProperties& props = frame->renderNode->properties();
if (props.getAlpha() <= 0) {
return;
}
// Perform clipping
if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) {
if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) {
frame->pendingDirty.setEmpty();
}
}
// apply all transforms
mapRect(props, frame->pendingDirty, &mHead->pendingDirty);
// project backwards if necessary
if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) {
// First, find our parent RenderNode:
DirtyStack* parentNode = findParentRenderNode(frame);
// Find our parent's projection receiver, which is what we project onto
DirtyStack* projectionReceiver = findProjectionReceiver(parentNode);
if (projectionReceiver) {
applyTransforms(frame, projectionReceiver);
projectionReceiver->pendingDirty.join(frame->pendingDirty);
}
frame->pendingDirty.setEmpty();
}
}
void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
mHead->pendingDirty.join(left, top, right, bottom);
}
void DamageAccumulator::peekAtDirty(SkRect* dest) const {
*dest = mHead->pendingDirty;
}
void DamageAccumulator::finish(SkRect* totalDirty) {
LOG_ALWAYS_FATAL_IF(mHead->prev != mHead, "Cannot finish, mismatched push/pop calls! %p vs. %p", mHead->prev, mHead);
// Root node never has a transform, so this is the fully mapped dirty rect
*totalDirty = mHead->pendingDirty;
totalDirty->roundOut(totalDirty);
mHead->pendingDirty.setEmpty();
}
} /* namespace uirenderer */
} /* namespace android */