diff options
author | Stan Iliev <stani@google.com> | 2016-10-17 16:26:15 -0400 |
---|---|---|
committer | Stan Iliev <stani@google.com> | 2016-10-31 14:27:02 -0400 |
commit | 021693b967a2c5556dddd183eb0247df4079e1ad (patch) | |
tree | 162c1da3b5fad315aa0591f16e3f66b899e1b6cc /libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp | |
parent | 99449eea6cfe174eba269b3cfff06e6533d6314e (diff) |
Implement SkiaRecordingCanvas, RenderNodeDrawable and other drawables.
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
Diffstat (limited to 'libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp')
-rw-r--r-- | libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp new file mode 100644 index 000000000000..8d77938ad1b9 --- /dev/null +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -0,0 +1,704 @@ +/* + * 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 |