You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
657 lines
26 KiB
657 lines
26 KiB
/*
|
|
* Copyright 2019 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 <math/vec4.h>
|
|
|
|
#include <renderengine/Mesh.h>
|
|
|
|
#include <ui/Rect.h>
|
|
#include <ui/Transform.h>
|
|
|
|
#include <utils/Log.h>
|
|
|
|
#include "GLSkiaShadowPort.h"
|
|
|
|
namespace android {
|
|
namespace renderengine {
|
|
namespace gl {
|
|
|
|
/**
|
|
* The shadow geometry logic and vertex generation code has been ported from skia shadow
|
|
* fast path OpenGL implementation to draw shadows around rects and rounded rects including
|
|
* circles.
|
|
*
|
|
* path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
|
|
*
|
|
* Modifications made:
|
|
* - Switched to using std lib math functions
|
|
* - Fall off function is implemented in vertex shader rather than a shadow texture
|
|
* - Removed transformations applied on the caster rect since the caster will be in local
|
|
* coordinate space and will be transformed by the vertex shader.
|
|
*/
|
|
|
|
static inline float divide_and_pin(float numer, float denom, float min, float max) {
|
|
if (denom == 0.0f) return min;
|
|
return std::clamp(numer / denom, min, max);
|
|
}
|
|
|
|
static constexpr auto SK_ScalarSqrt2 = 1.41421356f;
|
|
static constexpr auto kAmbientHeightFactor = 1.0f / 128.0f;
|
|
static constexpr auto kAmbientGeomFactor = 64.0f;
|
|
// Assuming that we have a light height of 600 for the spot shadow,
|
|
// the spot values will reach their maximum at a height of approximately 292.3077.
|
|
// We'll round up to 300 to keep it simple.
|
|
static constexpr auto kMaxAmbientRadius = 300 * kAmbientHeightFactor * kAmbientGeomFactor;
|
|
|
|
inline float AmbientBlurRadius(float height) {
|
|
return std::min(height * kAmbientHeightFactor * kAmbientGeomFactor, kMaxAmbientRadius);
|
|
}
|
|
inline float AmbientRecipAlpha(float height) {
|
|
return 1.0f + std::max(height * kAmbientHeightFactor, 0.0f);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Circle Data
|
|
//
|
|
// We have two possible cases for geometry for a circle:
|
|
|
|
// In the case of a normal fill, we draw geometry for the circle as an octagon.
|
|
static const uint16_t gFillCircleIndices[] = {
|
|
// enter the octagon
|
|
// clang-format off
|
|
0, 1, 8, 1, 2, 8,
|
|
2, 3, 8, 3, 4, 8,
|
|
4, 5, 8, 5, 6, 8,
|
|
6, 7, 8, 7, 0, 8,
|
|
// clang-format on
|
|
};
|
|
|
|
// For stroked circles, we use two nested octagons.
|
|
static const uint16_t gStrokeCircleIndices[] = {
|
|
// enter the octagon
|
|
// clang-format off
|
|
0, 1, 9, 0, 9, 8,
|
|
1, 2, 10, 1, 10, 9,
|
|
2, 3, 11, 2, 11, 10,
|
|
3, 4, 12, 3, 12, 11,
|
|
4, 5, 13, 4, 13, 12,
|
|
5, 6, 14, 5, 14, 13,
|
|
6, 7, 15, 6, 15, 14,
|
|
7, 0, 8, 7, 8, 15,
|
|
// clang-format on
|
|
};
|
|
|
|
#define SK_ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
|
|
static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
|
|
static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
|
|
static const int kVertsPerStrokeCircle = 16;
|
|
static const int kVertsPerFillCircle = 9;
|
|
|
|
static int circle_type_to_vert_count(bool stroked) {
|
|
return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
|
|
}
|
|
|
|
static int circle_type_to_index_count(bool stroked) {
|
|
return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
|
|
}
|
|
|
|
static const uint16_t* circle_type_to_indices(bool stroked) {
|
|
return stroked ? gStrokeCircleIndices : gFillCircleIndices;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// RoundRect Data
|
|
//
|
|
// The geometry for a shadow roundrect is similar to a 9-patch:
|
|
// ____________
|
|
// |_|________|_|
|
|
// | | | |
|
|
// | | | |
|
|
// | | | |
|
|
// |_|________|_|
|
|
// |_|________|_|
|
|
//
|
|
// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
|
|
// shows the upper part of the upper left corner. The bottom triangle would similarly be split
|
|
// into two triangles.)
|
|
// ________
|
|
// |\ \ |
|
|
// | \ \ |
|
|
// | \\ |
|
|
// | \|
|
|
// --------
|
|
//
|
|
// The center of the fan handles the curve of the corner. For roundrects where the stroke width
|
|
// is greater than the corner radius, the outer triangles blend from the curve to the straight
|
|
// sides. Otherwise these triangles will be degenerate.
|
|
//
|
|
// In the case where the stroke width is greater than the corner radius and the
|
|
// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
|
|
// This rectangle extends the coverage values of the center edges of the 9-patch.
|
|
// ____________
|
|
// |_|________|_|
|
|
// | |\ ____ /| |
|
|
// | | | | | |
|
|
// | | |____| | |
|
|
// |_|/______\|_|
|
|
// |_|________|_|
|
|
//
|
|
// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
|
|
|
|
static const uint16_t gRRectIndices[] = {
|
|
// clang-format off
|
|
// overstroke quads
|
|
// we place this at the beginning so that we can skip these indices when rendering as filled
|
|
0, 6, 25, 0, 25, 24,
|
|
6, 18, 27, 6, 27, 25,
|
|
18, 12, 26, 18, 26, 27,
|
|
12, 0, 24, 12, 24, 26,
|
|
|
|
// corners
|
|
0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
|
|
6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
|
|
12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
|
|
18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
|
|
|
|
// edges
|
|
0, 5, 11, 0, 11, 6,
|
|
6, 7, 19, 6, 19, 18,
|
|
18, 23, 17, 18, 17, 12,
|
|
12, 13, 1, 12, 1, 0,
|
|
|
|
// fill quad
|
|
// we place this at the end so that we can skip these indices when rendering as stroked
|
|
0, 6, 18, 0, 18, 12,
|
|
// clang-format on
|
|
};
|
|
|
|
// overstroke count
|
|
static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
|
|
// simple stroke count skips overstroke indices
|
|
static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4;
|
|
// fill count adds final quad to stroke count
|
|
static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
|
|
static const int kVertsPerStrokeRRect = 24;
|
|
static const int kVertsPerOverstrokeRRect = 28;
|
|
static const int kVertsPerFillRRect = 24;
|
|
|
|
static int rrect_type_to_vert_count(RRectType type) {
|
|
switch (type) {
|
|
case kFill_RRectType:
|
|
return kVertsPerFillRRect;
|
|
case kStroke_RRectType:
|
|
return kVertsPerStrokeRRect;
|
|
case kOverstroke_RRectType:
|
|
return kVertsPerOverstrokeRRect;
|
|
}
|
|
ALOGE("Invalid rect type: %d", type);
|
|
return -1;
|
|
}
|
|
|
|
static int rrect_type_to_index_count(RRectType type) {
|
|
switch (type) {
|
|
case kFill_RRectType:
|
|
return kIndicesPerFillRRect;
|
|
case kStroke_RRectType:
|
|
return kIndicesPerStrokeRRect;
|
|
case kOverstroke_RRectType:
|
|
return kIndicesPerOverstrokeRRect;
|
|
}
|
|
ALOGE("Invalid rect type: %d", type);
|
|
return -1;
|
|
}
|
|
|
|
static const uint16_t* rrect_type_to_indices(RRectType type) {
|
|
switch (type) {
|
|
case kFill_RRectType:
|
|
case kStroke_RRectType:
|
|
return gRRectIndices + 6 * 4;
|
|
case kOverstroke_RRectType:
|
|
return gRRectIndices;
|
|
}
|
|
ALOGE("Invalid rect type: %d", type);
|
|
return nullptr;
|
|
}
|
|
|
|
static void fillInCircleVerts(const Geometry& args, bool isStroked,
|
|
Mesh::VertexArray<vec2>& position,
|
|
Mesh::VertexArray<vec4>& shadowColor,
|
|
Mesh::VertexArray<vec3>& shadowParams) {
|
|
vec4 color = args.fColor;
|
|
float outerRadius = args.fOuterRadius;
|
|
float innerRadius = args.fInnerRadius;
|
|
float blurRadius = args.fBlurRadius;
|
|
float distanceCorrection = outerRadius / blurRadius;
|
|
|
|
const FloatRect& bounds = args.fDevBounds;
|
|
|
|
// The inner radius in the vertex data must be specified in normalized space.
|
|
innerRadius = innerRadius / outerRadius;
|
|
|
|
vec2 center = vec2(bounds.getWidth() / 2.0f, bounds.getHeight() / 2.0f);
|
|
float halfWidth = 0.5f * bounds.getWidth();
|
|
float octOffset = 0.41421356237f; // sqrt(2) - 1
|
|
int vertexCount = 0;
|
|
|
|
position[vertexCount] = center + vec2(-octOffset * halfWidth, -halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-octOffset, -1, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(octOffset * halfWidth, -halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(octOffset, -1, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(halfWidth, -octOffset * halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(1, -octOffset, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(halfWidth, octOffset * halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(1, octOffset, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(octOffset * halfWidth, halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(octOffset, 1, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(-octOffset * halfWidth, halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-octOffset, 1, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(-halfWidth, octOffset * halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-1, octOffset, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(-halfWidth, -octOffset * halfWidth);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-1, -octOffset, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
if (isStroked) {
|
|
// compute the inner ring
|
|
|
|
// cosine and sine of pi/8
|
|
float c = 0.923579533f;
|
|
float s = 0.382683432f;
|
|
float r = args.fInnerRadius;
|
|
|
|
position[vertexCount] = center + vec2(-s * r, -c * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-s * innerRadius, -c * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(s * r, -c * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(s * innerRadius, -c * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(c * r, -s * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(c * innerRadius, -s * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(c * r, s * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(c * innerRadius, s * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(s * r, c * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(s * innerRadius, c * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(-s * r, c * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-s * innerRadius, c * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(-c * r, s * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-c * innerRadius, s * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = center + vec2(-c * r, -s * r);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(-c * innerRadius, -s * innerRadius, distanceCorrection);
|
|
vertexCount++;
|
|
} else {
|
|
// filled
|
|
position[vertexCount] = center;
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
|
|
vertexCount++;
|
|
}
|
|
}
|
|
|
|
static void fillInRRectVerts(const Geometry& args, Mesh::VertexArray<vec2>& position,
|
|
Mesh::VertexArray<vec4>& shadowColor,
|
|
Mesh::VertexArray<vec3>& shadowParams) {
|
|
vec4 color = args.fColor;
|
|
float outerRadius = args.fOuterRadius;
|
|
|
|
const FloatRect& bounds = args.fDevBounds;
|
|
|
|
float umbraInset = args.fUmbraInset;
|
|
float minDim = 0.5f * std::min(bounds.getWidth(), bounds.getHeight());
|
|
if (umbraInset > minDim) {
|
|
umbraInset = minDim;
|
|
}
|
|
|
|
float xInner[4] = {bounds.left + umbraInset, bounds.right - umbraInset,
|
|
bounds.left + umbraInset, bounds.right - umbraInset};
|
|
float xMid[4] = {bounds.left + outerRadius, bounds.right - outerRadius,
|
|
bounds.left + outerRadius, bounds.right - outerRadius};
|
|
float xOuter[4] = {bounds.left, bounds.right, bounds.left, bounds.right};
|
|
float yInner[4] = {bounds.top + umbraInset, bounds.top + umbraInset, bounds.bottom - umbraInset,
|
|
bounds.bottom - umbraInset};
|
|
float yMid[4] = {bounds.top + outerRadius, bounds.top + outerRadius,
|
|
bounds.bottom - outerRadius, bounds.bottom - outerRadius};
|
|
float yOuter[4] = {bounds.top, bounds.top, bounds.bottom, bounds.bottom};
|
|
|
|
float blurRadius = args.fBlurRadius;
|
|
|
|
// In the case where we have to inset more for the umbra, our two triangles in the
|
|
// corner get skewed to a diamond rather than a square. To correct for that,
|
|
// we also skew the vectors we send to the shader that help define the circle.
|
|
// By doing so, we end up with a quarter circle in the corner rather than the
|
|
// elliptical curve.
|
|
|
|
// This is a bit magical, but it gives us the correct results at extrema:
|
|
// a) umbraInset == outerRadius produces an orthogonal vector
|
|
// b) outerRadius == 0 produces a diagonal vector
|
|
// And visually the corner looks correct.
|
|
vec2 outerVec = vec2(outerRadius - umbraInset, -outerRadius - umbraInset);
|
|
outerVec = normalize(outerVec);
|
|
// We want the circle edge to fall fractionally along the diagonal at
|
|
// (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
|
|
//
|
|
// Setting the components of the diagonal offset to the following value will give us that.
|
|
float diagVal = umbraInset / (SK_ScalarSqrt2 * (outerRadius - umbraInset) - outerRadius);
|
|
vec2 diagVec = vec2(diagVal, diagVal);
|
|
float distanceCorrection = umbraInset / blurRadius;
|
|
|
|
int vertexCount = 0;
|
|
// build corner by corner
|
|
for (int i = 0; i < 4; ++i) {
|
|
// inner point
|
|
position[vertexCount] = vec2(xInner[i], yInner[i]);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
// outer points
|
|
position[vertexCount] = vec2(xOuter[i], yInner[i]);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = vec2(xOuter[i], yMid[i]);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = vec2(xOuter[i], yOuter[i]);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(diagVec.x, diagVec.y, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = vec2(xMid[i], yOuter[i]);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
position[vertexCount] = vec2(xInner[i], yOuter[i]);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
|
|
vertexCount++;
|
|
}
|
|
|
|
// Add the additional vertices for overstroked rrects.
|
|
// Effectively this is an additional stroked rrect, with its
|
|
// parameters equal to those in the center of the 9-patch. This will
|
|
// give constant values across this inner ring.
|
|
if (kOverstroke_RRectType == args.fType) {
|
|
float inset = umbraInset + args.fInnerRadius;
|
|
|
|
// TL
|
|
position[vertexCount] = vec2(bounds.left + inset, bounds.top + inset);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
// TR
|
|
position[vertexCount] = vec2(bounds.right - inset, bounds.top + inset);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
// BL
|
|
position[vertexCount] = vec2(bounds.left + inset, bounds.bottom - inset);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
|
|
vertexCount++;
|
|
|
|
// BR
|
|
position[vertexCount] = vec2(bounds.right - inset, bounds.bottom - inset);
|
|
shadowColor[vertexCount] = color;
|
|
shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
|
|
vertexCount++;
|
|
}
|
|
}
|
|
|
|
int getVertexCountForGeometry(const Geometry& shadowGeometry) {
|
|
if (shadowGeometry.fIsCircle) {
|
|
return circle_type_to_vert_count(shadowGeometry.fType);
|
|
}
|
|
|
|
return rrect_type_to_vert_count(shadowGeometry.fType);
|
|
}
|
|
|
|
int getIndexCountForGeometry(const Geometry& shadowGeometry) {
|
|
if (shadowGeometry.fIsCircle) {
|
|
return circle_type_to_index_count(kStroke_RRectType == shadowGeometry.fType);
|
|
}
|
|
|
|
return rrect_type_to_index_count(shadowGeometry.fType);
|
|
}
|
|
|
|
void fillVerticesForGeometry(const Geometry& shadowGeometry, int /* vertexCount */,
|
|
Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
|
|
Mesh::VertexArray<vec3> shadowParams) {
|
|
if (shadowGeometry.fIsCircle) {
|
|
fillInCircleVerts(shadowGeometry, shadowGeometry.fIsStroked, position, shadowColor,
|
|
shadowParams);
|
|
} else {
|
|
fillInRRectVerts(shadowGeometry, position, shadowColor, shadowParams);
|
|
}
|
|
}
|
|
|
|
void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
|
|
int startingVertexOffset, uint16_t* indices) {
|
|
if (shadowGeometry.fIsCircle) {
|
|
const uint16_t* primIndices = circle_type_to_indices(shadowGeometry.fIsStroked);
|
|
for (int i = 0; i < indexCount; ++i) {
|
|
indices[i] = primIndices[i] + startingVertexOffset;
|
|
}
|
|
} else {
|
|
const uint16_t* primIndices = rrect_type_to_indices(shadowGeometry.fType);
|
|
for (int i = 0; i < indexCount; ++i) {
|
|
indices[i] = primIndices[i] + startingVertexOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void GetSpotParams(float occluderZ, float lightX, float lightY, float lightZ,
|
|
float lightRadius, float& blurRadius, float& scale, vec2& translate) {
|
|
float zRatio = divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
|
|
blurRadius = lightRadius * zRatio;
|
|
scale = divide_and_pin(lightZ, lightZ - occluderZ, 1.0f, 1.95f);
|
|
translate.x = -zRatio * lightX;
|
|
translate.y = -zRatio * lightY;
|
|
}
|
|
|
|
static std::unique_ptr<Geometry> getShadowGeometry(const vec4& color, const FloatRect& devRect,
|
|
float devRadius, float blurRadius,
|
|
float insetWidth) {
|
|
// An insetWidth > 1/2 rect width or height indicates a simple fill.
|
|
const bool isCircle = ((devRadius >= devRect.getWidth()) && (devRadius >= devRect.getHeight()));
|
|
|
|
FloatRect bounds = devRect;
|
|
float innerRadius = 0.0f;
|
|
float outerRadius = devRadius;
|
|
float umbraInset;
|
|
|
|
RRectType type = kFill_RRectType;
|
|
if (isCircle) {
|
|
umbraInset = 0;
|
|
} else {
|
|
umbraInset = std::max(outerRadius, blurRadius);
|
|
}
|
|
|
|
// If stroke is greater than width or height, this is still a fill,
|
|
// otherwise we compute stroke params.
|
|
if (isCircle) {
|
|
innerRadius = devRadius - insetWidth;
|
|
type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
|
|
} else {
|
|
if (insetWidth <= 0.5f * std::min(devRect.getWidth(), devRect.getHeight())) {
|
|
// We don't worry about a real inner radius, we just need to know if we
|
|
// need to create overstroke vertices.
|
|
innerRadius = std::max(insetWidth - umbraInset, 0.0f);
|
|
type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
|
|
}
|
|
}
|
|
const bool isStroked = (kStroke_RRectType == type);
|
|
return std::make_unique<Geometry>(Geometry{color, outerRadius, umbraInset, innerRadius,
|
|
blurRadius, bounds, type, isCircle, isStroked});
|
|
}
|
|
|
|
std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
|
|
float casterCornerRadius, float casterZ,
|
|
bool casterIsTranslucent,
|
|
const vec4& ambientColor) {
|
|
float devSpaceInsetWidth = AmbientBlurRadius(casterZ);
|
|
const float umbraRecipAlpha = AmbientRecipAlpha(casterZ);
|
|
const float devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha;
|
|
|
|
// Outset the shadow rrect to the border of the penumbra
|
|
float ambientPathOutset = devSpaceInsetWidth;
|
|
FloatRect outsetRect(casterRect);
|
|
outsetRect.left -= ambientPathOutset;
|
|
outsetRect.top -= ambientPathOutset;
|
|
outsetRect.right += ambientPathOutset;
|
|
outsetRect.bottom += ambientPathOutset;
|
|
|
|
float outsetRad = casterCornerRadius + ambientPathOutset;
|
|
if (casterIsTranslucent) {
|
|
// set a large inset to force a fill
|
|
devSpaceInsetWidth = outsetRect.getWidth();
|
|
}
|
|
|
|
return getShadowGeometry(ambientColor, outsetRect, std::abs(outsetRad), devSpaceAmbientBlur,
|
|
std::abs(devSpaceInsetWidth));
|
|
}
|
|
|
|
std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
|
|
float casterCornerRadius, float casterZ,
|
|
bool casterIsTranslucent, const vec4& spotColor,
|
|
const vec3& lightPosition, float lightRadius) {
|
|
float devSpaceSpotBlur;
|
|
float spotScale;
|
|
vec2 spotOffset;
|
|
GetSpotParams(casterZ, lightPosition.x, lightPosition.y, lightPosition.z, lightRadius,
|
|
devSpaceSpotBlur, spotScale, spotOffset);
|
|
// handle scale of radius due to CTM
|
|
const float srcSpaceSpotBlur = devSpaceSpotBlur;
|
|
|
|
// Adjust translate for the effect of the scale.
|
|
spotOffset.x += spotScale;
|
|
spotOffset.y += spotScale;
|
|
|
|
// Compute the transformed shadow rect
|
|
ui::Transform shadowTransform;
|
|
shadowTransform.set(spotOffset.x, spotOffset.y);
|
|
shadowTransform.set(spotScale, 0, 0, spotScale);
|
|
FloatRect spotShadowRect = shadowTransform.transform(casterRect);
|
|
float spotShadowRadius = casterCornerRadius * spotScale;
|
|
|
|
// Compute the insetWidth
|
|
float blurOutset = srcSpaceSpotBlur;
|
|
float insetWidth = blurOutset;
|
|
if (casterIsTranslucent) {
|
|
// If transparent, just do a fill
|
|
insetWidth += spotShadowRect.getWidth();
|
|
} else {
|
|
// For shadows, instead of using a stroke we specify an inset from the penumbra
|
|
// border. We want to extend this inset area so that it meets up with the caster
|
|
// geometry. The inset geometry will by default already be inset by the blur width.
|
|
//
|
|
// We compare the min and max corners inset by the radius between the original
|
|
// rrect and the shadow rrect. The distance between the two plus the difference
|
|
// between the scaled radius and the original radius gives the distance from the
|
|
// transformed shadow shape to the original shape in that corner. The max
|
|
// of these gives the maximum distance we need to cover.
|
|
//
|
|
// Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to
|
|
// that to get the full insetWidth.
|
|
float maxOffset;
|
|
if (casterCornerRadius <= 0.f) {
|
|
// Manhattan distance works better for rects
|
|
maxOffset = std::max(std::max(std::abs(spotShadowRect.left - casterRect.left),
|
|
std::abs(spotShadowRect.top - casterRect.top)),
|
|
std::max(std::abs(spotShadowRect.right - casterRect.right),
|
|
std::abs(spotShadowRect.bottom - casterRect.bottom)));
|
|
} else {
|
|
float dr = spotShadowRadius - casterCornerRadius;
|
|
vec2 upperLeftOffset = vec2(spotShadowRect.left - casterRect.left + dr,
|
|
spotShadowRect.top - casterRect.top + dr);
|
|
vec2 lowerRightOffset = vec2(spotShadowRect.right - casterRect.right - dr,
|
|
spotShadowRect.bottom - casterRect.bottom - dr);
|
|
maxOffset = sqrt(std::max(dot(upperLeftOffset, lowerRightOffset),
|
|
dot(lowerRightOffset, lowerRightOffset))) +
|
|
dr;
|
|
}
|
|
insetWidth += std::max(blurOutset, maxOffset);
|
|
}
|
|
|
|
// Outset the shadow rrect to the border of the penumbra
|
|
spotShadowRadius += blurOutset;
|
|
spotShadowRect.left -= blurOutset;
|
|
spotShadowRect.top -= blurOutset;
|
|
spotShadowRect.right += blurOutset;
|
|
spotShadowRect.bottom += blurOutset;
|
|
|
|
return getShadowGeometry(spotColor, spotShadowRect, std::abs(spotShadowRadius),
|
|
2.0f * devSpaceSpotBlur, std::abs(insetWidth));
|
|
}
|
|
|
|
void fillShadowTextureData(uint8_t* data, size_t shadowTextureWidth) {
|
|
for (int i = 0; i < shadowTextureWidth; i++) {
|
|
const float d = 1 - i / ((shadowTextureWidth * 1.0f) - 1.0f);
|
|
data[i] = static_cast<uint8_t>((exp(-4.0f * d * d) - 0.018f) * 255);
|
|
}
|
|
}
|
|
|
|
} // namespace gl
|
|
} // namespace renderengine
|
|
} // namespace android
|