blob: ba5a3bba5046f4cdc15b4663ba77ee568f703321 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/graphite/render/AnalyticRRectRenderStep.h"
#include "src/core/SkRRectPriv.h"
#include "src/gpu/graphite/DrawParams.h"
#include "src/gpu/graphite/DrawWriter.h"
#include "src/gpu/graphite/render/CommonDepthStencilSettings.h"
// This RenderStep is flexible and can draw filled rectangles, filled quadrilaterals with per-edge
// AA, filled rounded rectangles with arbitrary corner radii, stroked rectangles with any join,
// and stroked rounded rectangles with circular corners (each corner can be different or square).
// We combine all of these together to maximize batching across simple geometric draws and reduce
// the number pipeline specializations. Additionally, these primitives are the most common
// operations and help us avoid triggering MSAA.
//
// Each of these "primitives" is represented by a single instance. The instance attributes are
// flexible enough to describe any of the above shapes without relying on uniforms to define its
// operation. The attributes encode shape as follows:
//
// float4 xRadiiOrFlags - if any components is > 0, the instance represents a filled round rect
// with elliptical corners and these values specify the X radii in top-left CW order.
// Otherwise, if .x < -1, the instance represents a stroked [round] rect and .y holds the
// stroke radius and .z holds the join limit (matching StrokeStyle's conventions).
// Else it's a filled quadrilateral with per-edge AA defined by each component: aa != 0.
// float4 radiiOrQuadXs - if in filled round rect mode, these values provide the Y radii in
// top-left CW order. If in stroked [round] rect mode, these values provide the circular
// corner radii (same order). Otherwise, when in per-edge quad mode, these values provide
// the X coordinates of the quadrilateral (same order).
// float4 ltrbOrQuadYs - if in filled round rect mode or stroked [round] rect mode, these values
// define the LTRB edge coordinates of the rectangle surrounding the round rect (or the
// rect itself when the radii are 0s). Otherwise, in per-edge quad mode, these values provide
// the Y coordinates of the quadrilateral.
//
// From the other direction, shapes produce instance values like:
// - filled rect: [-1 -1 -1 -1] [L R R L] [T T B B]
// - stroked rect: [-2 stroke join 0] [0 0 0 0] [L T R B]
// - filled rrect: [xRadii(tl,tr,br,bl)] [yRadii(tl,tr,br,bl)] [L T R B]
// - stroked rrect: [-2 stroke join 0] [radii(tl,tr,br,bl)] [L T R B]
// - per-edge quad: [aa(tl,tr,br,bl) ? -1 : 0] [xs(tl,tr,br,bl)] [ys(tl,tr,br,bl)]
//
// This encoding relies on the fact that a valid SkRRect with all x radii equal to 0 must have
// y radii equal to 0 (so it's a rectangle and we can treat it as a quadrilateral with
// all edges AA'ed). This avoids other encodings' inability to represent a quad with all edges
// anti-aliased (e.g. checking for negatives in xRadiiOrFlags to turn on per-edge mode).
//
// From this encoding, data can be unpacked for each corner, which are equivalent under
// rotational symmetry. A corner can have an outer curve, be mitered, or be beveled. It can
// have an inner curve, an inner miter, or fill the interior. Per-edge quads are always mitered
// and fill the interior, but the vertices are placed such that the edge coverage ramps can
// collapse to 0 area on non-AA edges.
//
// The vertices that describe each corner are placed so that edges, miters, and bevels calculate
// coverage by interpolating a varying and then clamping in the fragment shader. Triangles that
// cover the inner and outer curves calculate distance to the curve within the fragment shader.
//
// See https://docs.google.com/presentation/d/1MCPstNsSlDBhR8CrsJo0r-cZNbu-sEJEvU9W94GOJoY/edit?usp=sharing
// for diagrams and explanation of how the geometry is defined.
namespace skgpu::graphite {
static skvx::float4 loadXRadii(const SkRRect& rrect) {
// We swizzle X and Y radii for the anti-diagonal corners to match overall winding of the rrect
// when processed as vertices on the GPU.
return skvx::float4{rrect.radii(SkRRect::kUpperLeft_Corner).fX,
rrect.radii(SkRRect::kUpperRight_Corner).fY,
rrect.radii(SkRRect::kLowerRight_Corner).fX,
rrect.radii(SkRRect::kLowerLeft_Corner).fY};
}
static skvx::float4 loadYRadii(const SkRRect& rrect) {
return skvx::float4{rrect.radii(SkRRect::kUpperLeft_Corner).fY,
rrect.radii(SkRRect::kUpperRight_Corner).fX,
rrect.radii(SkRRect::kLowerRight_Corner).fY,
rrect.radii(SkRRect::kLowerLeft_Corner).fX};
}
static float localAARadius(const Transform& t, const SkV2& p) {
// TODO: This should be the logic for Transform::scaleFactor()
// [m00 m01 * m03] [f(u,v)]
// Assuming M = [m10 m11 * m13], define the projected p'(u,v) = [g(u,v)] where
// [ * * * * ]
// [m30 m31 * m33]
// [x] [u]
// f(u,v) = x(u,v) / w(u,v), g(u,v) = y(u,v) / w(u,v) and [y] = M*[v]
// [*] = [0]
// [w] [1]
//
// x(u,v) = m00*u + m01*v + m03
// y(u,v) = m10*u + m11*v + m13
// w(u,v) = m30*u + m31*v + m33
//
// dx/du = m00, dx/dv = m01,
// dy/du = m10, dy/dv = m11
// dw/du = m30, dw/dv = m31
//
// df/du = (dx/du*w - x*dw/du)/w^2 = (m00*w - m30*x)/w^2
// df/dv = (dx/dv*w - x*dw/dv)/w^2 = (m01*w - m31*x)/w^2
// dg/du = (dy/du*w - y*dw/du)/w^2 = (m10*w - m30*y)/w^2
// dg/dv = (dy/dv*w - y*dw/du)/w^2 = (m11*w - m31*y)/w^2
//
// Singular values of [df/du df/dv] define perspective correct minimum and maximum scale factors
// [dg/du dg/dv]
// for M evaluated at (u,v)
const SkM44& matrix = t.matrix();
SkV4 devP = matrix.map(p.x, p.y, 0.f, 1.f);
const float dxdu = matrix.rc(0,0);
const float dxdv = matrix.rc(0,1);
const float dydu = matrix.rc(1,0);
const float dydv = matrix.rc(1,1);
const float dwdu = matrix.rc(3,0);
const float dwdv = matrix.rc(3,1);
float invW2 = sk_ieee_float_divide(1.f, (devP.w * devP.w));
// non-persp has invW2 = 1, devP.w = 1, dwdu = 0, dwdv = 0
float dfdu = (devP.w*dxdu - devP.x*dwdu) * invW2; // non-persp -> dxdu -> m00
float dfdv = (devP.w*dxdv - devP.x*dwdv) * invW2; // non-persp -> dxdv -> m01
float dgdu = (devP.w*dydu - devP.y*dwdu) * invW2; // non-persp -> dydu -> m10
float dgdv = (devP.w*dydv - devP.y*dwdv) * invW2; // non-persp -> dydv -> m11
// no-persp, these are the singular values of [m00,m01][m10,m11], which is just the upper 2x2
// and equivalent to SkMatrix::getMinmaxScales().
float s1 = dfdu*dfdu + dfdv*dfdv + dgdu*dgdu + dgdv*dgdv;
float e = dfdu*dfdu + dfdv*dfdv - dgdu*dgdu - dgdv*dgdv;
float f = dfdu*dgdu + dfdv*dgdv;
float s2 = SkScalarSqrt(e*e + 4*f*f);
float singular1 = SkScalarSqrt(0.5f * (s1 + s2));
float singular2 = SkScalarSqrt(0.5f * (s1 - s2));
// singular1 and 2 represent the minimum and maximum scale factors at that transformed point.
// Moving 1 from 'p' before transforming will move at least minimum and at most maximum from
// the transformed point. Thus moving between [1/max, 1/min] pre-transformation means post
// transformation moves between [1,max/min] so using 1/min as the local AA radius ensures that
// the post-transformed point is at least 1px away from the original.
float aaRadius = sk_ieee_float_divide(1.f, std::min(singular1, singular2));
if (sk_float_isfinite(aaRadius)) {
return aaRadius;
} else {
// Treat NaNs and infinities as +inf, which will always trigger the inset self-intersection
// logic that snaps inner vertices to the center instead of insetting by the local AA radius
return SK_FloatInfinity;
}
}
static float localAARadius(const Transform& t, const Rect& bounds) {
// Use the maximum radius of the 4 corners so that every local vertex uses the same offset
// even if it's more conservative on some corners (when the min/max scale isn't constant due
// to perspective).
if (t.type() < Transform::Type::kProjection) {
// Scale factors are constant, so the point doesn't really matter
return localAARadius(t, SkV2{0.f, 0.f});
} else {
// TODO can we share calculation here?
float tl = localAARadius(t, SkV2{bounds.left(), bounds.top()});
float tr = localAARadius(t, SkV2{bounds.right(), bounds.top()});
float br = localAARadius(t, SkV2{bounds.right(), bounds.bot()});
float bl = localAARadius(t, SkV2{bounds.left(), bounds.bot()});
return std::max(std::max(tl, tr), std::max(bl, br));
}
}
static bool cornerInsetsIntersect(const SkRRect& rrect, float maxInset) {
return // Horizontal insets would intersect opposite corner's curve
maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerLeft_Corner).fX ||
maxInset >= rrect.width() - rrect.radii(SkRRect::kLowerRight_Corner).fX ||
maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperLeft_Corner).fX ||
maxInset >= rrect.width() - rrect.radii(SkRRect::kUpperRight_Corner).fX ||
// Vertical insets would intersect opposite corner's curve
maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerLeft_Corner).fY ||
maxInset >= rrect.height() - rrect.radii(SkRRect::kLowerRight_Corner).fY ||
maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperLeft_Corner).fY ||
maxInset >= rrect.height() - rrect.radii(SkRRect::kUpperRight_Corner).fY;
}
// Represents the per-vertex attributes used in each instance.
struct Vertex {
SkV2 fPosition;
SkV2 fNormal;
float fStrokeControl;
float fMirrorOffset;
float fCenterWeight;
};
// Allowed values for the center weight instance value (selected at record time based on style
// and transform), and are defined such that when (insance-weight > vertex-weight) is true, the
// vertex should be snapped to the center instead of its regular calculation.
static constexpr float kDontSnapToCenter = 0.f;
static constexpr float kFillCenter = 1.f;
static constexpr float kInsetsIntersect = 2.f;
static constexpr int kCornerVertexCount = 19; // sk_VertexID is divided by this in SkSL
static constexpr int kVertexCount = 4 * kCornerVertexCount;
static constexpr int kIndexCount = 153;
static size_t indexBufferSize() { return kIndexCount * sizeof(uint16_t); }
static void writeIndexBuffer(VertexWriter writer, size_t size) {
SkASSERT(indexBufferSize() == size);
static constexpr uint16_t kTL = 0 * kCornerVertexCount;
static constexpr uint16_t kTR = 1 * kCornerVertexCount;
static constexpr uint16_t kBR = 2 * kCornerVertexCount;
static constexpr uint16_t kBL = 3 * kCornerVertexCount;
static const uint16_t kIndices[kIndexCount] = {
// Exterior AA ramp outset
kTL+0,kTL+6,kTL+1,kTL+7,kTL+2,kTL+8,kTL+3,kTL+8,kTL+4,kTL+9,kTL+5,kTL+9,
kTR+0,kTR+6,kTR+1,kTR+7,kTR+2,kTR+8,kTR+3,kTR+8,kTR+4,kTR+9,kTR+5,kTR+9,
kBR+0,kBR+6,kBR+1,kBR+7,kBR+2,kBR+8,kBR+3,kBR+8,kBR+4,kBR+9,kBR+5,kBR+9,
kBL+0,kBL+6,kBL+1,kBL+7,kBL+2,kBL+8,kBL+3,kBL+8,kBL+4,kBL+9,kBL+5,kBL+9,
kTL+0,kTL+6,kTL+6, // close and extra vertex to jump to next strip
// Outer to central curve
kTL+6,kTL+10,kTL+7,kTL+11,kTL+8,kTL+12,kTL+9,kTL+13,
kTR+6,kTR+10,kTR+7,kTR+11,kTR+8,kTR+12,kTR+9,kTR+13,
kBR+6,kBR+10,kBR+7,kBR+11,kBR+8,kBR+12,kBR+9,kBR+13,
kBL+6,kBL+10,kBL+7,kBL+11,kBL+8,kBL+12,kBL+9,kBL+13,
kTL+6,kTL+10,kTL+10, // close and extra vertex to jump to next strip
// Center to inner curve's insets
kTL+10,kTL+14,kTL+11,kTL+15,kTL+12,kTL+16,kTL+13,kTL+16,
kTR+10,kTR+14,kTR+11,kTR+15,kTR+12,kTR+16,kTR+13,kTR+16,
kBR+10,kBR+14,kBR+11,kBR+15,kBR+12,kBR+16,kBR+13,kBR+16,
kBL+10,kBL+14,kBL+11,kBL+15,kBL+12,kBL+16,kBL+13,kBL+16,
kTL+10,kTL+14,kTL+14, // close and extra vertex to jump to next strip
// Inner inset to center of shape
kTL+14,kTL+17,kTL+15,kTL+17,kTL+16,kTL+16,kTL+18,kTR+14,
kTR+14,kTR+17,kTR+15,kTR+17,kTR+16,kTR+16,kTR+18,kBR+14,
kBR+14,kBR+17,kBR+15,kBR+17,kBR+16,kBR+16,kBR+18,kBL+14,
kBL+14,kBL+17,kBL+15,kBL+17,kBL+16,kBL+16,kBL+18,kTL+14 // close
};
writer << kIndices;
}
static size_t vertexBufferSize() { return kVertexCount * sizeof(Vertex); }
static void writeVertexBuffer(VertexWriter writer, size_t size) {
SkASSERT(vertexBufferSize() == size);
// Allowed values for the stroke control vertex attribute. This is multiplied with the stroke
// radius of the instance to get the final effect on the corner positions. When the stroke
// radius is zero (for fills or hairlines), the three possible curves are coincident.
static constexpr float kOuterStroke = 1.f;
static constexpr float kInnerStroke = -1.f;
static constexpr float kCenterStroke = 0.f;
// Allowed values for the mirror offset attribute. The full normalized position for a vertex is
// position + join-scale*mirror-offset*position.yx; where join-scale is a scalar determined by
// the bevel, round, or miter join style of the instance and corner's radii.
static constexpr float kNoOffset = 0.f;
static constexpr float kMirrorOffset = 1.f;
// Allowed values for each vertex's center weight (assuming only the allowed instance center
// weights are used, defined earlier).
static constexpr float kNeverSnapToCenter = 2.f;
static constexpr float kSnapIfInsetsIntersect = 1.f;
static constexpr float kSnapForFills = 0.f;
static constexpr float kHR2 = 0.5f * SK_FloatSqrt2; // "half root 2"
// This template is repeated 4 times in the vertex buffer, for each of the four corners.
// The vertex ID is used to determine which corner the normalized position is transformed to.
static constexpr Vertex kCornerTemplate[kCornerVertexCount] = {
// Device-space AA outsets from outer curve
{ {1.0f, 0.0f}, { 1.0f, 0.0f}, kOuterStroke, kNoOffset, kNeverSnapToCenter },
{ {1.0f, 0.0f}, { 1.0f, 0.0f}, kOuterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {1.0f, 0.0f}, { kHR2, kHR2}, kOuterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { kHR2, kHR2}, kOuterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { 0.0f, 1.0f}, kOuterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { 0.0f, 1.0f}, kOuterStroke, kNoOffset, kNeverSnapToCenter },
// Outer anchors (no local or device-space normal outset)
{ {1.0f, 0.0f}, { 0.0f, 0.0f}, kOuterStroke, kNoOffset, kNeverSnapToCenter },
{ {1.0f, 0.0f}, { 0.0f, 0.0f}, kOuterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { 0.0f, 0.0f}, kOuterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { 0.0f, 0.0f}, kOuterStroke, kNoOffset, kNeverSnapToCenter },
// Center of stroke (equivalent to outer anchors when filling)
{ {1.0f, 0.0f}, { 0.0f, 0.0f}, kCenterStroke, kNoOffset, kNeverSnapToCenter },
{ {1.0f, 0.0f}, { 0.0f, 0.0f}, kCenterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { 0.0f, 0.0f}, kCenterStroke, kMirrorOffset, kNeverSnapToCenter },
{ {0.0f, 1.0f}, { 0.0f, 0.0f}, kCenterStroke, kNoOffset, kNeverSnapToCenter },
// Inner AA insets from inner curve
{ {1.0f, 0.0f}, {-1.0f, 0.0f}, kInnerStroke, kNoOffset, kSnapIfInsetsIntersect },
{ {0.5f, 0.5f}, {-kHR2, -kHR2}, kInnerStroke, kMirrorOffset, kSnapIfInsetsIntersect },
{ {0.0f, 1.0f}, { 0.0f, -1.0f}, kInnerStroke, kNoOffset, kSnapIfInsetsIntersect },
// Center filling vertices (equal to inner AA insets unless instance weight = kFillCenter)
{ {0.5f, 0.5f}, {-kHR2, -kHR2}, kInnerStroke, kMirrorOffset, kSnapForFills },
{ {0.0f, 1.0f}, { 0.0f, -1.0f}, kInnerStroke, kNoOffset, kSnapForFills },
};
writer << kCornerTemplate // TL
<< kCornerTemplate // TR
<< kCornerTemplate // BR
<< kCornerTemplate; // BL
}
AnalyticRRectRenderStep::AnalyticRRectRenderStep()
: RenderStep("AnalyticRRectRenderStep",
"",
Flags::kPerformsShading |
Flags::kEmitsCoverage,
/*uniforms=*/{},
PrimitiveType::kTriangleStrip,
kDirectDepthGreaterPass,
/*vertexAttrs=*/{
{"position", VertexAttribType::kFloat2, SkSLType::kFloat2},
{"normal", VertexAttribType::kFloat2, SkSLType::kFloat2},
{"strokeControl", VertexAttribType::kFloat, SkSLType::kFloat},
{"mirrorOffset", VertexAttribType::kFloat, SkSLType::kFloat},
{"centerWeight", VertexAttribType::kFloat, SkSLType::kFloat}
},
/*instanceAttrs=*/
{{"xRadiiOrFlags", VertexAttribType::kFloat4, SkSLType::kFloat4},
{"radiiOrQuadXs", VertexAttribType::kFloat4, SkSLType::kFloat4},
{"ltrbOrQuadYs", VertexAttribType::kFloat4, SkSLType::kFloat4},
// xy stores center of rrect in local coords, z stores a control value
// added to each vertex's centerWeight to handle snapping when needed.
// w stores the local AA radius calculated from the full transform.
{"center", VertexAttribType::kFloat4, SkSLType::kFloat4},
// TODO: pack depth and ssboIndex into 32-bits
{"depth", VertexAttribType::kFloat, SkSLType::kFloat},
{"ssboIndex", VertexAttribType::kInt, SkSLType::kInt},
{"mat0", VertexAttribType::kFloat3, SkSLType::kFloat3},
{"mat1", VertexAttribType::kFloat3, SkSLType::kFloat3},
{"mat2", VertexAttribType::kFloat3, SkSLType::kFloat3},
// We need the first two columns of the inverse transform to calculate
// the normal matrix.
{"invMat0", VertexAttribType::kFloat3, SkSLType::kFloat3},
{"invMat1", VertexAttribType::kFloat3, SkSLType::kFloat3}},
/*varyings=*/{}) {}
AnalyticRRectRenderStep::~AnalyticRRectRenderStep() {}
const char* AnalyticRRectRenderStep::vertexSkSL() const {
// TODO: Move this into a module
return R"(
const float kMiterScale = 1.0;
const float kBevelScale = 0.0;
const float kRoundScale = 0.41421356237; // sqrt(2)-1
int cornerID = sk_VertexID / 19; // KEEP IN SYNC WITH kCornerVertexCount
float4 xs, ys; // should be BR, TR, TL, BL
float2 cornerRadii = float2(0.0);
float strokeRadius = 0.0; // fill and hairline are differentiated by center weighting
if (xRadiiOrFlags.x < -1.0) {
// Stroked rect or round rect
strokeRadius = xRadiiOrFlags.y;
cornerRadii = float2(radiiOrQuadXs[cornerID]); // strokes require circular corners
xs = ltrbOrQuadYs.LRRL;
ys = ltrbOrQuadYs.TTBB;
} else if (any(greaterThan(xRadiiOrFlags, float4(0.0)))) {
// Filled round rect
cornerRadii = float2(xRadiiOrFlags[cornerID], radiiOrQuadXs[cornerID]);
xs = ltrbOrQuadYs.LRRL;
ys = ltrbOrQuadYs.TTBB;
} else {
// Per-edge quadrilateral, so we have to calculate the corner's basis from the
// quad's edges.
xs = radiiOrQuadXs;
ys = ltrbOrQuadYs;
}
float2 corner = float2(xs.xyzw[cornerID], ys.xyzw[cornerID]);
float2 cornerCW = float2(xs.yzwx[cornerID], ys.yzwx[cornerID]);
float2 cornerCCW = float2(xs.wxyz[cornerID], ys.wxyz[cornerID]);
float2 xAxis = normalize(corner - cornerCW);
float2 yAxis = normalize(corner - cornerCCW);
// Determine the amount of mirror offsetting to apply based on fill/hairline/stroke join
// TODO should this analysis be done on the CPU and we upload 4 joinScales instead of
// 1 join style that has to be combined with the per-corner radii?
float joinScale;
if (cornerRadii.x > 0.000124 || cornerRadii.y > 0.000124) {
// A rounded corner is always rounded regardless of style
joinScale = kRoundScale;
} else if (strokeRadius > 0.0 && xRadiiOrFlags.z <= 0.0) {
// A rect corner of a non-hairline stroke changes based on the join type
joinScale = xRadiiOrFlags.z == 0.0 ? kBevelScale : kRoundScale;
} else {
// A filled or hairline rect corner is always mitered
joinScale = kMiterScale;
}
float2 scale = cornerRadii + strokeRadius*strokeControl;
float2 localPos;
if (center.z > centerWeight) {
// It's either a fill and this vertex is designated to fill to the center, or it's a
// self-intersecting stroke that snaps to the center so geometry stays well-defined.
localPos = center.xy;
} else {
float2 p = scale*(position + joinScale*mirrorOffset*position.yx);
if (strokeControl < 0.0) {
// An inset, so check for and avoid self-intersections
float localAARadius = center.w;
float2 maxInset = scale - localAARadius;
if (any(lessThan(maxInset, float2(0.0)))) {
p = min(maxInset, float2(0.0));
} else {
p += localAARadius * normal;
}
}
// Orient and place p relative to the corner's location in the local rectangle
localPos = float2x2(xAxis, yAxis)*(p - cornerRadii) + corner;
}
float3 devPos = float3x3(mat0, mat1, mat2)*localPos.xy1;
if (strokeControl > 0.0 && (normal.x > 0.0 || normal.y > 0.0)) {
// We need a device-space normal added to devPos. Transform the local normal by the
// normal matrix (A^-1)^T where A = M*T(corner)*scale; but since we know the structure
// of T(corner)*scale and that we're in 2D, we can skip computing the entire matrix.
float3 localNx = calc_line_eq(-yAxis, corner);
float3 localNy = calc_line_eq( xAxis, corner);
float sx = (scale.y + 0.000124) / (scale.x + 0.000124);
float2 nx = sx * normal.x * float2(dot(invMat0, localNx), dot(invMat1, localNx));
float2 ny = normal.y * float2(dot(invMat0, localNy), dot(invMat1, localNy));
if (joinScale == 1.0 && all(greaterThan(normal, float2(0.0)))) {
// Produce a bisecting vector in device space.
nx = normalize(nx);
ny = normalize(ny);
if (dot(nx, ny) < -0.8) {
// Normals are in nearly opposite directions, so adjust to avoid float error.
float s = sign(cross_length_2d(nx, ny));
nx = s*ortho(nx);
ny = -s*ortho(ny);
}
}
// Adding the normal components together directly results in what we'd have
// calculated if we'd just transformed 'normal' in one go, assuming they weren't
// normalized in the if-block above. If they were normalized, the sum equals the
// bisector between the original nx and ny.
//
// We multiply by W so that after perspective division the new point is offset by the
// normal.
devPos.xy += devPos.z * normalize(nx + ny);
}
// Write out final results
stepLocalCoords = localPos;
float4 devPosition = float4(devPos.xy, devPos.z*depth, devPos.z);
)";
}
const char* AnalyticRRectRenderStep::fragmentCoverageSkSL() const {
// TODO: Actually implement this for linear edges (that get clamp a varying to [0,1]) and for
// corners calculating distance to an ellipse.
return R"(
outputCoverage = half4(1.0);
)";
}
void AnalyticRRectRenderStep::writeVertices(DrawWriter* writer,
const DrawParams& params,
int ssboIndex) const {
SkASSERT(params.geometry().isShape());
const Shape& shape = params.geometry().shape();
BindBufferInfo vertices =
writer->bufferManager()->getStaticBuffer(BufferType::kVertex,
writeVertexBuffer, vertexBufferSize);
BindBufferInfo indices = writer->bufferManager()->getStaticBuffer(BufferType::kIndex,
writeIndexBuffer,
indexBufferSize);
DrawWriter::Instances instance{*writer, vertices, indices, kIndexCount};
auto vw = instance.append(1);
// The bounds of a rect is the rect, and the bounds of a rrect is tight
Rect bounds = params.geometry().bounds();
const float aaRadius = localAARadius(params.transform(), bounds);
float centerWeight;
if (params.isStroke()) {
SkASSERT(params.strokeStyle().halfWidth() >= 0.f);
SkASSERT(shape.isRect() ||
(shape.isRRect() && SkRRectPriv::AllCornersCircular(shape.rrect())));
const float maxInset = aaRadius + params.strokeStyle().halfWidth();
bool insetsIntersect = any(2.f * maxInset >= bounds.size());
skvx::float4 cornerRadii;
if (shape.isRRect()) {
// X and Y radii are the same, but each corner could be different. Take X arbitrarily.
cornerRadii = loadXRadii(shape.rrect());
insetsIntersect |= cornerInsetsIntersect(shape.rrect(), maxInset);
} else {
// All four corner radii are 0s for a rectangle
cornerRadii = 0.f;
}
// Write a negative value outside [-1, 0] to signal a stroked shape, then the style params,
// followed by corner radii and bounds.
vw << -2.f << params.strokeStyle().halfWidth() << params.strokeStyle().joinLimit() << 0.f
<< cornerRadii << bounds.ltrb();
centerWeight = insetsIntersect ? kInsetsIntersect : kDontSnapToCenter;
} else {
// TODO: Add quadrilateral support to Shape with per-edge flags.
if (shape.isRect() || (shape.isRRect() && shape.rrect().isRect())) {
// Rectangles (or rectangles embedded in an SkRRect) are converted to the quadrilateral
// case, but with all edges anti-aliased (== -1).
skvx::float4 ltrb = bounds.ltrb();
vw << /*edge flags*/ skvx::float4(-1.f)
<< /*xs*/ skvx::shuffle<0,2,2,0>(ltrb)
<< /*ys*/ skvx::shuffle<1,1,3,3>(ltrb);
// For simplicity, it's assumed arbitrary quad insets could self-intersect, so force
// all interior vertices to the center.
centerWeight = kInsetsIntersect;
} else {
// A filled rounded rectangle
const SkRRect& rrect = shape.rrect();
SkASSERT(any(loadXRadii(rrect) > 0.f)); // If not, the shader won't detect this case
vw << loadXRadii(rrect) << loadYRadii(rrect) << bounds.ltrb();
centerWeight = cornerInsetsIntersect(rrect, aaRadius) ? kInsetsIntersect : kFillCenter;
}
}
// All instance types share the remaining instance attribute definitions
const SkM44& m = params.transform().matrix();
const SkM44& invM = params.transform().inverse();
vw << bounds.center() << centerWeight << aaRadius
<< params.order().depthAsFloat()
<< ssboIndex
<< m.rc(0,0) << m.rc(1,0) << m.rc(3,0) // mat0
<< m.rc(0,1) << m.rc(1,1) << m.rc(3,1) // mat1
<< m.rc(0,3) << m.rc(1,3) << m.rc(3,3) // mat2
<< invM.rc(0,0) << invM.rc(1,0) << invM.rc(3,0) // invMat0
<< invM.rc(0,1) << invM.rc(1,1) << invM.rc(3,1); // invMat1
}
void AnalyticRRectRenderStep::writeUniformsAndTextures(const DrawParams&,
PipelineDataGatherer*) const {
// All data is uploaded as instance attributes, so no uniforms are needed.
}
} // namespace skgpu::graphite