blob: 9db47c3ba0904470f5cab0f40b49c753d46af1dd [file] [log] [blame]
/*
* 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 <log/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 applyMatrix(const SkMatrix* transform, SkRect* rect) {
if (transform && !transform->isIdentity()) {
if (CC_LIKELY(!transform->hasPerspective())) {
transform->mapRect(rect);
} 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
rect->setLTRB(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
}
}
}
static inline void applyMatrix(const SkMatrix& transform, SkRect* rect) {
return applyMatrix(&transform, rect);
}
static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
if (in.isEmpty()) return;
SkRect temp(in);
if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) {
const StretchEffect& stretch = props.layerProperties().getStretchEffect();
if (!stretch.isEmpty()) {
applyMatrix(stretch.makeLinearStretch(props.getWidth(), props.getHeight()), &temp);
}
}
applyMatrix(props.getTransformMatrix(), &temp);
if (props.getStaticMatrix()) {
applyMatrix(props.getStaticMatrix(), &temp);
} else if (props.getAnimationMatrix()) {
applyMatrix(props.getAnimationMatrix(), &temp);
}
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;
}
}
static void computeTransformImpl(const DirtyStack* frame, const DirtyStack* end,
Matrix4* outMatrix) {
while (frame != end) {
switch (frame->type) {
case TransformRenderNode:
frame->renderNode->applyViewPropertyTransforms(*outMatrix);
break;
case TransformMatrix4:
outMatrix->multiply(*frame->matrix4);
break;
case TransformNone:
// nothing to be done
break;
default:
LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d",
frame->type);
}
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(SkRect::MakeIWH(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();
}
DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const {
DirtyStack* frame = mHead;
const auto& headProperties = mHead->renderNode->properties();
float startWidth = headProperties.getWidth();
float startHeight = headProperties.getHeight();
while (frame->prev != frame) {
if (frame->type == TransformRenderNode) {
const auto& renderNode = frame->renderNode;
const auto& frameRenderNodeProperties = renderNode->properties();
const auto& effect =
frameRenderNodeProperties.layerProperties().getStretchEffect();
const float width = (float) frameRenderNodeProperties.getWidth();
const float height = (float) frameRenderNodeProperties.getHeight();
if (!effect.isEmpty()) {
Matrix4 stretchMatrix;
computeTransformImpl(mHead, frame, &stretchMatrix);
Rect stretchRect = Rect(0.f, 0.f, startWidth, startHeight);
stretchMatrix.mapRect(stretchRect);
return StretchResult{
.stretchEffect = &effect,
.childRelativeBounds = SkRect::MakeLTRB(
stretchRect.left,
stretchRect.top,
stretchRect.right,
stretchRect.bottom
),
.width = width,
.height = height
};
}
}
frame = frame->prev;
}
return StretchResult{};
}
} /* namespace uirenderer */
} /* namespace android */