Implement SkiaRecordingCanvas, RenderNodeDrawable, GLFunctorDrawable, LayerDrawable, StartReorderBarrierDrawable, EndReorderBarrierDrawable. Move AnimatedRoundRect and AnimatedCircle in a separate file. All Skia pipeline files are moved in hwui/pipeline/skia folder. Add unit tests for RenderNodeDrawable, StartReorderBarrierDrawable, EndReorderBarrierDrawable and SkiaRecordingCanvas. Test: I tested manually on 6P devices and did run the unit tests. Change-Id: If2a347bd1fc4689953822294ce5bf98c7f3f57c7
705 lines
30 KiB
C++
705 lines
30 KiB
C++
/*
|
|
* 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 "SkiaFrameRenderer.h"
|
|
|
|
#include <SkBlurMask.h>
|
|
#include <SkBlurMaskFilter.h>
|
|
#include <SkGaussianEdgeShader.h>
|
|
#include <SkPathOps.h>
|
|
#include <SkRRectsGaussianEdgeShader.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 (unsigned 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;
|
|
});
|
|
|
|
SkASSERT(!mChildren.empty());
|
|
|
|
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;
|
|
}
|
|
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;
|
|
SkASSERT(!zChildren.empty());
|
|
|
|
/**
|
|
* 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);
|
|
childNode->forceDraw(canvas);
|
|
|
|
drawIndex++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param canvas the destination for the shadow draws
|
|
* @param shape the shape casting the shadow
|
|
* @param casterZValue the Z value of the caster RRect
|
|
* @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
|
|
* @param draw the function used to draw 'shape'
|
|
*/
|
|
template <typename Shape, typename F>
|
|
static void DrawAmbientShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
|
|
float ambientAlpha, F&& draw) {
|
|
if (ambientAlpha <= 0) {
|
|
return;
|
|
}
|
|
|
|
const float kHeightFactor = 1.f/128.f;
|
|
const float kGeomFactor = 64;
|
|
|
|
float umbraAlpha = 1 / (1 + SkMaxScalar(casterZValue*kHeightFactor, 0));
|
|
float radius = casterZValue*kHeightFactor*kGeomFactor;
|
|
|
|
sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
|
|
SkBlurMask::ConvertRadiusToSigma(radius), SkBlurMaskFilter::kNone_BlurFlag);
|
|
SkPaint paint;
|
|
paint.setAntiAlias(true);
|
|
paint.setMaskFilter(std::move(mf));
|
|
paint.setARGB(ambientAlpha*umbraAlpha, 0, 0, 0);
|
|
|
|
draw(shape, paint);
|
|
}
|
|
|
|
/**
|
|
* @param canvas the destination for the shadow draws
|
|
* @param shape the shape casting the shadow
|
|
* @param casterZValue the Z value of the caster RRect
|
|
* @param lightPos the position of the light casting the shadow
|
|
* @param lightWidth
|
|
* @param spotAlpha the maximum alpha value to use when drawing the spot shadow
|
|
* @param draw the function used to draw 'shape'
|
|
*/
|
|
template <typename Shape, typename F>
|
|
static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
|
|
float spotAlpha, F&& draw) {
|
|
if (spotAlpha <= 0) {
|
|
return;
|
|
}
|
|
|
|
const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
|
|
float zRatio = casterZValue / (lightPos.z - casterZValue);
|
|
// clamp
|
|
if (zRatio < 0.0f) {
|
|
zRatio = 0.0f;
|
|
} else if (zRatio > 0.95f) {
|
|
zRatio = 0.95f;
|
|
}
|
|
|
|
float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio;
|
|
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
|
|
sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
|
|
SkBlurMask::ConvertRadiusToSigma(blurRadius), SkBlurMaskFilter::kNone_BlurFlag);
|
|
|
|
SkPaint paint;
|
|
paint.setAntiAlias(true);
|
|
paint.setMaskFilter(std::move(mf));
|
|
paint.setARGB(spotAlpha, 0, 0, 0);
|
|
|
|
// approximate projection by translating and scaling projected offset of bounds center
|
|
// TODO: compute the actual 2D projection
|
|
SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
|
|
canvas->scale(scale, scale);
|
|
SkPoint center = SkPoint::Make(shape.getBounds().centerX(), shape.getBounds().centerY());
|
|
SkMatrix ctmInverse;
|
|
if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
|
|
ALOGW("Matrix is degenerate. Will not render shadow!");
|
|
return;
|
|
}
|
|
SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
|
|
ctmInverse.mapPoints(&lightPos2D, 1);
|
|
canvas->translate(zRatio*(center.fX - lightPos2D.fX), zRatio*(center.fY - lightPos2D.fY));
|
|
|
|
draw(shape, paint);
|
|
}
|
|
|
|
#define MAX_BLUR_RADIUS 16383.75f
|
|
#define MAX_PAD 64
|
|
|
|
/**
|
|
* @param casterRect the rectangle bounds of the RRect casting the shadow
|
|
* @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
|
|
* @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
|
|
* @param spotAlpha the maximum alpha value to use when drawing the spot shadow
|
|
* @param casterAlpha the alpha value of the RRect casting the shadow (0.0-1.0 range)
|
|
* @param casterZValue the Z value of the caster RRect
|
|
* @param scaleFactor the scale needed to map from src-space to device-space
|
|
* @param canvas the destination for the shadow draws
|
|
*/
|
|
static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadius,
|
|
SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue,
|
|
SkScalar scaleFactor, SkCanvas* canvas) {
|
|
SkASSERT(cornerRadius >= 0.0f);
|
|
|
|
// For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
|
|
const SkScalar minRadius = 0.5f / scaleFactor;
|
|
|
|
const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
|
|
SkScalarHalf(casterRect.height()));
|
|
const bool isRect = casterCornerRadius <= minRadius;
|
|
|
|
sk_sp<SkShader> edgeShader(SkGaussianEdgeShader::Make());
|
|
|
|
if (ambientAlpha > 0.0f) {
|
|
static const float kHeightFactor = 1.0f / 128.0f;
|
|
static const float kGeomFactor = 64.0f;
|
|
|
|
SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
|
|
// the device-space radius sent to the blur shader must fit in 14.2 fixed point
|
|
if (srcSpaceAmbientRadius*scaleFactor > MAX_BLUR_RADIUS) {
|
|
srcSpaceAmbientRadius = MAX_BLUR_RADIUS/scaleFactor;
|
|
}
|
|
const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
|
|
const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
|
|
|
|
// For the ambient rrect, we inset the offset rect by half the srcSpaceAmbientRadius
|
|
// to get our stroke shape.
|
|
SkScalar ambientPathOutset = std::max(ambientOffset - srcSpaceAmbientRadius * 0.5f,
|
|
minRadius);
|
|
|
|
SkRRect ambientRRect;
|
|
const SkRect temp = casterRect.makeOutset(ambientPathOutset, ambientPathOutset);
|
|
if (isOval) {
|
|
ambientRRect = SkRRect::MakeOval(temp);
|
|
} else if (isRect) {
|
|
ambientRRect = SkRRect::MakeRectXY(temp, ambientPathOutset, ambientPathOutset);
|
|
} else {
|
|
ambientRRect = SkRRect::MakeRectXY(temp, casterCornerRadius + ambientPathOutset,
|
|
casterCornerRadius + ambientPathOutset);
|
|
}
|
|
|
|
SkPaint paint;
|
|
paint.setAntiAlias(true);
|
|
paint.setStyle(SkPaint::kStroke_Style);
|
|
// we outset the stroke a little to cover up AA on the interior edge
|
|
float pad = 0.5f;
|
|
paint.setStrokeWidth(srcSpaceAmbientRadius + 2.0f * pad);
|
|
// handle scale of radius and pad due to CTM
|
|
pad *= scaleFactor;
|
|
const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
|
|
SkASSERT(devSpaceAmbientRadius <= MAX_BLUR_RADIUS);
|
|
SkASSERT(pad < MAX_PAD);
|
|
// convert devSpaceAmbientRadius to 14.2 fixed point and place in the R & G components
|
|
// convert pad to 6.2 fixed point and place in the B component
|
|
uint16_t iDevSpaceAmbientRadius = (uint16_t)(4.0f * devSpaceAmbientRadius);
|
|
paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, iDevSpaceAmbientRadius >> 8,
|
|
iDevSpaceAmbientRadius & 0xff, (unsigned char)(4.0f * pad)));
|
|
|
|
paint.setShader(edgeShader);
|
|
canvas->drawRRect(ambientRRect, paint);
|
|
}
|
|
|
|
if (spotAlpha > 0.0f) {
|
|
const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
|
|
float zRatio = casterZValue / (lightPos.z - casterZValue);
|
|
// clamp
|
|
if (zRatio < 0.0f) {
|
|
zRatio = 0.0f;
|
|
} else if (zRatio > 0.95f) {
|
|
zRatio = 0.95f;
|
|
}
|
|
|
|
const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
|
|
SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
|
|
// the device-space radius sent to the blur shader must fit in 14.2 fixed point
|
|
if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) {
|
|
srcSpaceSpotRadius = MAX_BLUR_RADIUS/scaleFactor;
|
|
}
|
|
|
|
SkRRect spotRRect;
|
|
if (isOval) {
|
|
spotRRect = SkRRect::MakeOval(casterRect);
|
|
} else if (isRect) {
|
|
spotRRect = SkRRect::MakeRectXY(casterRect, minRadius, minRadius);
|
|
} else {
|
|
spotRRect = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
|
|
}
|
|
|
|
SkRRect spotShadowRRect;
|
|
// Compute the scale and translation for the spot shadow.
|
|
const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
|
|
spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
|
|
|
|
SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
|
|
spotShadowRRect.rect().centerY());
|
|
SkMatrix ctmInverse;
|
|
if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
|
|
ALOGW("Matrix is degenerate. Will not render spot shadow!");
|
|
return;
|
|
}
|
|
SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
|
|
ctmInverse.mapPoints(&lightPos2D, 1);
|
|
const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
|
|
zRatio*(center.fY - lightPos2D.fY));
|
|
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
|
|
// We want to extend the stroked area in so that it meets up with the caster
|
|
// geometry. The stroked geometry will, by definition already be inset half the
|
|
// stroke width but we also have to account for the scaling.
|
|
// We also add 1/2 to cover up AA on the interior edge.
|
|
SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(casterRect.fLeft),
|
|
SkTAbs(casterRect.fRight)), SkTMax(SkTAbs(casterRect.fTop),
|
|
SkTAbs(casterRect.fBottom)));
|
|
SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) +
|
|
scaleOffset + 0.5f;
|
|
|
|
// Compute area
|
|
SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
|
|
SkScalar strokedArea = 2.0f*strokeWidth * (spotShadowRRect.width()
|
|
+ spotShadowRRect.height());
|
|
SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius)
|
|
* (spotShadowRRect.width() + srcSpaceSpotRadius);
|
|
|
|
SkPaint paint;
|
|
paint.setAntiAlias(true);
|
|
|
|
// If the area of the stroked geometry is larger than the fill geometry, just fill it.
|
|
if (strokedArea > filledArea || casterAlpha < 1.0f) {
|
|
paint.setStyle(SkPaint::kStrokeAndFill_Style);
|
|
paint.setStrokeWidth(srcSpaceSpotRadius);
|
|
} else {
|
|
// Since we can't have unequal strokes, inset the shadow rect so the inner
|
|
// and outer edges of the stroke will land where we want.
|
|
SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount/2.0f, insetAmount/2.0f);
|
|
SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount/2.0f,
|
|
minRadius);
|
|
spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
|
|
paint.setStyle(SkPaint::kStroke_Style);
|
|
paint.setStrokeWidth(strokeWidth);
|
|
}
|
|
|
|
// handle scale of radius and pad due to CTM
|
|
const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
|
|
SkASSERT(devSpaceSpotRadius <= MAX_BLUR_RADIUS);
|
|
|
|
const SkScalar devSpaceSpotPad = 0;
|
|
SkASSERT(devSpaceSpotPad < MAX_PAD);
|
|
|
|
// convert devSpaceSpotRadius to 14.2 fixed point and place in the R & G
|
|
// components convert devSpaceSpotPad to 6.2 fixed point and place in the B component
|
|
uint16_t iDevSpaceSpotRadius = (uint16_t)(4.0f * devSpaceSpotRadius);
|
|
paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, iDevSpaceSpotRadius >> 8,
|
|
iDevSpaceSpotRadius & 0xff, (unsigned char)(4.0f * devSpaceSpotPad)));
|
|
paint.setShader(edgeShader);
|
|
|
|
canvas->translate(spotOffset.fX, spotOffset.fY);
|
|
canvas->drawRRect(spotShadowRRect, paint);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param casterRect the rectangle bounds of the RRect casting the shadow
|
|
* @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
|
|
* @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
|
|
* @param spotAlpha the maximum alpha value to use when drawing the spot shadow
|
|
* @param casterZValue the Z value of the caster RRect
|
|
* @param scaleFactor the scale needed to map from src-space to device-space
|
|
* @param clipRR the oval or rect with which the drawn roundrect must be intersected
|
|
* @param canvas the destination for the shadow draws
|
|
*/
|
|
static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCornerRadius,
|
|
SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterZValue, SkScalar scaleFactor,
|
|
const SkRRect& clipRR, SkCanvas* canvas) {
|
|
SkASSERT(cornerRadius >= 0.0f);
|
|
|
|
const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
|
|
SkScalarHalf(casterRect.height()));
|
|
|
|
if (ambientAlpha > 0.0f) {
|
|
static const float kHeightFactor = 1.0f / 128.0f;
|
|
static const float kGeomFactor = 64.0f;
|
|
|
|
const SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
|
|
const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
|
|
|
|
const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
|
|
const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
|
|
|
|
const SkRect srcSpaceAmbientRect = casterRect.makeOutset(ambientOffset, ambientOffset);
|
|
SkRect devSpaceAmbientRect;
|
|
canvas->getTotalMatrix().mapRect(&devSpaceAmbientRect, srcSpaceAmbientRect);
|
|
|
|
SkRRect devSpaceAmbientRRect;
|
|
if (isOval) {
|
|
devSpaceAmbientRRect = SkRRect::MakeOval(devSpaceAmbientRect);
|
|
} else {
|
|
const SkScalar devSpaceCornerRadius = scaleFactor * (casterCornerRadius + ambientOffset);
|
|
devSpaceAmbientRRect = SkRRect::MakeRectXY(devSpaceAmbientRect, devSpaceCornerRadius,
|
|
devSpaceCornerRadius);
|
|
}
|
|
|
|
const SkRect srcSpaceAmbClipRect = clipRR.rect().makeOutset(ambientOffset, ambientOffset);
|
|
SkRect devSpaceAmbClipRect;
|
|
canvas->getTotalMatrix().mapRect(&devSpaceAmbClipRect, srcSpaceAmbClipRect);
|
|
SkRRect devSpaceAmbientClipRR;
|
|
if (clipRR.isOval()) {
|
|
devSpaceAmbientClipRR = SkRRect::MakeOval(devSpaceAmbClipRect);
|
|
} else {
|
|
SkASSERT(clipRR.isRect());
|
|
devSpaceAmbientClipRR = SkRRect::MakeRect(devSpaceAmbClipRect);
|
|
}
|
|
|
|
SkRect cover = srcSpaceAmbClipRect;
|
|
if (!cover.intersect(srcSpaceAmbientRect)) {
|
|
return;
|
|
}
|
|
|
|
SkPaint paint;
|
|
paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, 0, 0, 0));
|
|
paint.setShader(SkRRectsGaussianEdgeShader::Make(devSpaceAmbientRRect,
|
|
devSpaceAmbientClipRR, devSpaceAmbientRadius));
|
|
canvas->drawRect(cover, paint);
|
|
}
|
|
|
|
if (spotAlpha > 0.0f) {
|
|
const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
|
|
float zRatio = casterZValue / (lightPos.z - casterZValue);
|
|
// clamp
|
|
if (zRatio < 0.0f) {
|
|
zRatio = 0.0f;
|
|
} else if (zRatio > 0.95f) {
|
|
zRatio = 0.95f;
|
|
}
|
|
|
|
const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
|
|
const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
|
|
const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
|
|
|
|
// Compute the scale and translation for the spot shadow.
|
|
const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
|
|
const SkMatrix spotMatrix = SkMatrix::MakeScale(scale, scale);
|
|
|
|
SkRect srcSpaceScaledRect = casterRect;
|
|
spotMatrix.mapRect(&srcSpaceScaledRect);
|
|
srcSpaceScaledRect.outset(SkScalarHalf(srcSpaceSpotRadius),
|
|
SkScalarHalf(srcSpaceSpotRadius));
|
|
|
|
SkRRect srcSpaceSpotRRect;
|
|
if (isOval) {
|
|
srcSpaceSpotRRect = SkRRect::MakeOval(srcSpaceScaledRect);
|
|
} else {
|
|
srcSpaceSpotRRect = SkRRect::MakeRectXY(srcSpaceScaledRect, casterCornerRadius * scale,
|
|
casterCornerRadius * scale);
|
|
}
|
|
|
|
SkPoint center = SkPoint::Make(srcSpaceSpotRRect.rect().centerX(),
|
|
srcSpaceSpotRRect.rect().centerY());
|
|
SkMatrix ctmInverse;
|
|
if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
|
|
ALOGW("Matrix is degenerate. Will not render spot shadow!");
|
|
return;
|
|
}
|
|
SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
|
|
ctmInverse.mapPoints(&lightPos2D, 1);
|
|
const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
|
|
zRatio*(center.fY - lightPos2D.fY));
|
|
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
canvas->translate(spotOffset.fX, spotOffset.fY);
|
|
|
|
SkRect devSpaceScaledRect;
|
|
canvas->getTotalMatrix().mapRect(&devSpaceScaledRect, srcSpaceScaledRect);
|
|
|
|
SkRRect devSpaceSpotRRect;
|
|
if (isOval) {
|
|
devSpaceSpotRRect = SkRRect::MakeOval(devSpaceScaledRect);
|
|
} else {
|
|
const SkScalar devSpaceScaledCornerRadius = casterCornerRadius * scale * scaleFactor;
|
|
devSpaceSpotRRect = SkRRect::MakeRectXY(devSpaceScaledRect, devSpaceScaledCornerRadius,
|
|
devSpaceScaledCornerRadius);
|
|
}
|
|
|
|
SkPaint paint;
|
|
paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, 0, 0, 0));
|
|
|
|
SkRect srcSpaceScaledClipRect = clipRR.rect();
|
|
spotMatrix.mapRect(&srcSpaceScaledClipRect);
|
|
srcSpaceScaledClipRect.outset(SkScalarHalf(srcSpaceSpotRadius),
|
|
SkScalarHalf(srcSpaceSpotRadius));
|
|
|
|
SkRect devSpaceScaledClipRect;
|
|
canvas->getTotalMatrix().mapRect(&devSpaceScaledClipRect, srcSpaceScaledClipRect);
|
|
SkRRect devSpaceSpotClipRR;
|
|
if (clipRR.isOval()) {
|
|
devSpaceSpotClipRR = SkRRect::MakeOval(devSpaceScaledClipRect);
|
|
} else {
|
|
SkASSERT(clipRR.isRect());
|
|
devSpaceSpotClipRR = SkRRect::MakeRect(devSpaceScaledClipRect);
|
|
}
|
|
|
|
paint.setShader(SkRRectsGaussianEdgeShader::Make(devSpaceSpotRRect, devSpaceSpotClipRR,
|
|
devSpaceSpotRadius));
|
|
|
|
SkRect cover = srcSpaceScaledClipRect;
|
|
if (!cover.intersect(srcSpaceSpotRRect.rect())) {
|
|
return;
|
|
}
|
|
|
|
canvas->drawRect(cover, paint);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param casterRect the rectangle bounds of the RRect casting the shadow
|
|
* @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
|
|
* @param casterClipRect a rectangular clip that must be intersected with the
|
|
* shadow-casting RRect prior to casting the shadow
|
|
* @param revealClip a circular clip that must be interested with the castClipRect
|
|
* and the shadow-casting rect prior to casting the shadow
|
|
* @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
|
|
* @param spotAlpha the maximum alpha value to use when drawing the spot shadow
|
|
* @param casterAlpha the alpha value of the RRect casting the shadow (0.0-1.0 range)
|
|
* @param casterZValue the Z value of the caster RRect
|
|
* @param canvas the destination for the shadow draws
|
|
*
|
|
* We have special cases for 4 round rect shadow draws:
|
|
* 1) a RRect clipped by a reveal animation
|
|
* 2) a RRect clipped by a rectangle
|
|
* 3) an unclipped RRect with non-uniform scale
|
|
* 4) an unclipped RRect with uniform scale
|
|
* 1,2 and 4 require that the scale is uniform.
|
|
* 1 and 2 require that rects stay rects.
|
|
*/
|
|
static bool DrawShadowsAsRRects(const SkRect& casterRect, SkScalar casterCornerRadius,
|
|
const SkRect& casterClipRect, const RevealClip& revealClip, SkScalar ambientAlpha,
|
|
SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue, SkCanvas* canvas) {
|
|
SkScalar scaleFactors[2];
|
|
if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
|
|
ALOGW("Matrix is degenerate. Will not render shadow!");
|
|
return false;
|
|
}
|
|
|
|
// The casterClipRect will contain the casterRect when bounds clipping is disabled
|
|
bool casterIsClippedByRect = !casterClipRect.contains(casterRect);
|
|
bool uniformScale = scaleFactors[0] == scaleFactors[1];
|
|
|
|
if (revealClip.willClip()) {
|
|
if (casterIsClippedByRect || !uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
|
|
return false; // Fall back to the slow path since PathOps are required
|
|
}
|
|
|
|
const float revealRadius = revealClip.getRadius();
|
|
SkRect revealClipRect = SkRect::MakeLTRB(revealClip.getX()-revealRadius,
|
|
revealClip.getY()-revealRadius, revealClip.getX()+revealRadius,
|
|
revealClip.getY()+revealRadius);
|
|
SkRRect revealClipRR = SkRRect::MakeOval(revealClipRect);
|
|
|
|
DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
|
|
casterZValue, scaleFactors[0], revealClipRR, canvas);
|
|
return true;
|
|
}
|
|
|
|
if (casterIsClippedByRect) {
|
|
if (!uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
|
|
return false; // Fall back to the slow path since PathOps are required
|
|
}
|
|
|
|
SkRRect casterClipRR = SkRRect::MakeRect(casterClipRect);
|
|
|
|
DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
|
|
casterZValue, scaleFactors[0], casterClipRR, canvas);
|
|
return true;
|
|
}
|
|
|
|
// The fast path needs uniform scale
|
|
if (!uniformScale) {
|
|
SkRRect casterRR = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
|
|
DrawAmbientShadowGeneral(canvas, casterRR, casterZValue, ambientAlpha,
|
|
[&](const SkRRect& rrect, const SkPaint& paint) {
|
|
canvas->drawRRect(rrect, paint);
|
|
});
|
|
DrawSpotShadowGeneral(canvas, casterRR, casterZValue, spotAlpha,
|
|
[&](const SkRRect& rrect, const SkPaint& paint) {
|
|
canvas->drawRRect(rrect, paint);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
DrawRRectShadows(casterRect, casterCornerRadius, ambientAlpha, spotAlpha, casterAlpha,
|
|
casterZValue, scaleFactors[0], canvas);
|
|
return true;
|
|
}
|
|
|
|
// 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 = SkiaFrameRenderer::getAmbientShadowAlpha()*casterAlpha;
|
|
float spotAlpha = SkiaFrameRenderer::getSpotShadowAlpha()*casterAlpha;
|
|
const float casterZValue = casterProperties.getZ();
|
|
|
|
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::MakeLargest();
|
|
if (clippedToBounds) {
|
|
Rect clipBounds;
|
|
casterProperties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
|
|
casterClipRect = clipBounds.toSkRect();
|
|
}
|
|
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
|
|
SkMatrix shadowMatrix;
|
|
mat4 hwuiMatrix(caster->getRecordedMatrix());
|
|
// TODO we don't pass the optional boolean to treat it as a 4x4 matrix
|
|
caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix);
|
|
hwuiMatrix.copyTo(shadowMatrix);
|
|
canvas->concat(shadowMatrix);
|
|
|
|
const Outline& casterOutline = casterProperties.getOutline();
|
|
Rect possibleRect;
|
|
float radius;
|
|
if (casterOutline.getAsRoundRect(&possibleRect, &radius)) {
|
|
if (DrawShadowsAsRRects(possibleRect.toSkRect(), radius, casterClipRect, revealClip,
|
|
ambientAlpha, spotAlpha, casterAlpha, casterZValue, canvas)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Hard cases and calls to general shadow code
|
|
const SkPath* casterOutlinePath = casterProperties.getOutline().getPath();
|
|
|
|
// holds temporary SkPath to store the result of intersections
|
|
SkPath tmpPath;
|
|
const SkPath* casterPath = casterOutlinePath;
|
|
|
|
// TODO: In to following course of code that calculates the final shape, is there an optimal
|
|
// of doing the Op calculations?
|
|
// intersect the shadow-casting path with the reveal, if present
|
|
if (revealClipPath) {
|
|
Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, &tmpPath);
|
|
casterPath = &tmpPath;
|
|
}
|
|
|
|
// intersect the shadow-casting path with the clipBounds, if present
|
|
if (clippedToBounds) {
|
|
SkPath clipBoundsPath;
|
|
clipBoundsPath.addRect(casterClipRect);
|
|
Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, &tmpPath);
|
|
casterPath = &tmpPath;
|
|
}
|
|
|
|
DrawAmbientShadowGeneral(canvas, *casterPath, casterZValue, ambientAlpha,
|
|
[&](const SkPath& path, const SkPaint& paint) {
|
|
canvas->drawPath(path, paint);
|
|
});
|
|
|
|
DrawSpotShadowGeneral(canvas, *casterPath, casterZValue, spotAlpha,
|
|
[&](const SkPath& path, const SkPaint& paint) {
|
|
canvas->drawPath(path, paint);
|
|
});
|
|
}
|
|
|
|
}; // namespace skiapipeline
|
|
}; // namespace uirenderer
|
|
}; // namespace android
|