/*
* Copyright (C) 2016 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 "ReorderBarrierDrawables.h"
#include "RenderNode.h"
#include "SkiaDisplayList.h"
#include "SkiaPipeline.h"
#include <SkPathOps.h>
#include <SkShadowUtils.h>
namespace android {
namespace uirenderer {
namespace skiapipeline {
StartReorderBarrierDrawable::StartReorderBarrierDrawable(SkiaDisplayList* data)
: mEndChildIndex(0), mBeginChildIndex(data->mChildNodes.size()), mDisplayList(data) {}
void StartReorderBarrierDrawable::onDraw(SkCanvas* canvas) {
if (mChildren.empty()) {
// mChildren is allocated and initialized only the first time onDraw is called and cached
// for
// subsequent calls
mChildren.reserve(mEndChildIndex - mBeginChildIndex + 1);
for (int i = mBeginChildIndex; i <= mEndChildIndex; i++) {
mChildren.push_back(const_cast<RenderNodeDrawable*>(&mDisplayList->mChildNodes[i]));
}
}
std::stable_sort(mChildren.begin(), mChildren.end(),
[](RenderNodeDrawable* a, RenderNodeDrawable* b) {
const float aZValue = a->getNodeProperties().getZ();
const float bZValue = b->getNodeProperties().getZ();
return aZValue < bZValue;
});
size_t drawIndex = 0;
const size_t endIndex = mChildren.size();
while (drawIndex < endIndex) {
RenderNodeDrawable* childNode = mChildren[drawIndex];
SkASSERT(childNode);
const float casterZ = childNode->getNodeProperties().getZ();
if (casterZ >= -NON_ZERO_EPSILON) { // draw only children with negative Z
return;
}
SkAutoCanvasRestore acr(canvas, true);
// Since we're drawing out of recording order, the child's matrix needs to be applied to the
// canvas. In in-order drawing, the canvas already has the child's matrix applied.
canvas->setMatrix(mDisplayList->mParentMatrix);
canvas->concat(childNode->getRecordedMatrix());
childNode->forceDraw(canvas);
drawIndex++;
}
}
EndReorderBarrierDrawable::EndReorderBarrierDrawable(StartReorderBarrierDrawable* startBarrier)
: mStartBarrier(startBarrier) {
mStartBarrier->mEndChildIndex = mStartBarrier->mDisplayList->mChildNodes.size() - 1;
}
#define SHADOW_DELTA 0.1f
void EndReorderBarrierDrawable::onDraw(SkCanvas* canvas) {
auto& zChildren = mStartBarrier->mChildren;
/**
* Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
* with very similar Z heights to draw together.
*
* This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
* underneath both, and neither's shadow is drawn on top of the other.
*/
size_t drawIndex = 0;
const size_t endIndex = zChildren.size();
while (drawIndex < endIndex // draw only children with positive Z
&& zChildren[drawIndex]->getNodeProperties().getZ() <= NON_ZERO_EPSILON)
drawIndex++;
size_t shadowIndex = drawIndex;
float lastCasterZ = 0.0f;
while (shadowIndex < endIndex || drawIndex < endIndex) {
if (shadowIndex < endIndex) {
const float casterZ = zChildren[shadowIndex]->getNodeProperties().getZ();
// attempt to render the shadow if the caster about to be drawn is its caster,
// OR if its caster's Z value is similar to the previous potential caster
if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
this->drawShadow(canvas, zChildren[shadowIndex]);
lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
shadowIndex++;
continue;
}
}
RenderNodeDrawable* childNode = zChildren[drawIndex];
SkASSERT(childNode);
SkAutoCanvasRestore acr(canvas, true);
// Since we're drawing out of recording order, the child's matrix needs to be applied to the
// canvas. In in-order drawing, the canvas already has the child's matrix applied.
canvas->setMatrix(mStartBarrier->mDisplayList->mParentMatrix);
canvas->concat(childNode->getRecordedMatrix());
childNode->forceDraw(canvas);
drawIndex++;
}
}
static SkColor multiplyAlpha(SkColor color, float alpha) {
return SkColorSetA(color, alpha * SkColorGetA(color));
}
// copied from FrameBuilder::deferShadow
void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster) {
const RenderProperties& casterProperties = caster->getNodeProperties();
if (casterProperties.getAlpha() <= 0.0f || casterProperties.getOutline().getAlpha() <= 0.0f ||
!casterProperties.getOutline().getPath() || casterProperties.getScaleX() == 0 ||
casterProperties.getScaleY() == 0) {
// no shadow to draw
return;
}
const SkScalar casterAlpha =
casterProperties.getAlpha() * casterProperties.getOutline().getAlpha();
if (casterAlpha <= 0.0f) {
return;
}
float ambientAlpha = (SkiaPipeline::getAmbientShadowAlpha() / 255.f) * casterAlpha;
float spotAlpha = (SkiaPipeline::getSpotShadowAlpha() / 255.f) * casterAlpha;
const RevealClip& revealClip = casterProperties.getRevealClip();
const SkPath* revealClipPath = revealClip.getPath();
if (revealClipPath && revealClipPath->isEmpty()) {
// An empty reveal clip means nothing is drawn
return;
}
bool clippedToBounds = casterProperties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS;
SkRect casterClipRect = SkRect::MakeEmpty();
if (clippedToBounds) {
Rect clipBounds;
casterProperties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
casterClipRect = clipBounds.toSkRect();
if (casterClipRect.isEmpty()) {
// An empty clip rect means nothing is drawn
return;
}
}
SkAutoCanvasRestore acr(canvas, true);
// Since we're drawing out of recording order, the child's matrix needs to be applied to the
// canvas. In in-order drawing, the canvas already has the child's matrix applied.
canvas->setMatrix(mStartBarrier->mDisplayList->mParentMatrix);
SkMatrix shadowMatrix;
mat4 hwuiMatrix(caster->getRecordedMatrix());
// TODO we don't pass the optional boolean to treat it as a 4x4 matrix
// applyViewPropertyTransforms gets the same matrix, which render nodes apply with
// RenderNodeDrawable::setViewProperties as a part if their draw.
caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix);
hwuiMatrix.copyTo(shadowMatrix);
canvas->concat(shadowMatrix);
// default the shadow-casting path to the outline of the caster
const SkPath* casterPath = casterProperties.getOutline().getPath();
// intersect the shadow-casting path with the clipBounds, if present
if (clippedToBounds && !casterClipRect.contains(casterPath->getBounds())) {
casterPath = caster->getRenderNode()->getClippedOutline(casterClipRect);
}
// intersect the shadow-casting path with the reveal, if present
SkPath tmpPath; // holds temporary SkPath to store the result of intersections
if (revealClipPath) {
Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, &tmpPath);
tmpPath.setIsVolatile(true);
casterPath = &tmpPath;
}
const Vector3 lightPos = SkiaPipeline::getLightCenter();
SkPoint3 skiaLightPos = SkPoint3::Make(lightPos.x, lightPos.y, lightPos.z);
SkPoint3 zParams;
if (shadowMatrix.hasPerspective()) {
// get the matrix with the full 3D transform
mat4 zMatrix;
caster->getRenderNode()->applyViewPropertyTransforms(zMatrix, true);
zParams = SkPoint3::Make(zMatrix[2], zMatrix[6], zMatrix[mat4::kTranslateZ]);
} else {
zParams = SkPoint3::Make(0, 0, casterProperties.getZ());
}
SkColor ambientColor = multiplyAlpha(casterProperties.getAmbientShadowColor(), ambientAlpha);
SkColor spotColor = multiplyAlpha(casterProperties.getSpotShadowColor(), spotAlpha);
SkShadowUtils::DrawShadow(
canvas, *casterPath, zParams, skiaLightPos, SkiaPipeline::getLightRadius(),
ambientColor, spotColor,
casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0);
}
} // namespace skiapipeline
} // namespace uirenderer
} // namespace android