blob: 27824c6dc3469a88a8d11c36a0fd32fa2bcaf72e [file] [log] [blame]
Jim Van Verthc5903412016-11-17 15:27:09 -05001/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "src/gpu/ops/GrShadowRRectOp.h"
Robert Phillips7c525e62018-06-12 10:11:12 -04009
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "include/private/GrRecordingContext.h"
11#include "src/core/SkRRectPriv.h"
Greg Daniel6f5441a2020-01-28 17:02:49 -050012#include "src/gpu/GrBitmapTextureMaker.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "src/gpu/GrDrawOpTest.h"
14#include "src/gpu/GrMemoryPool.h"
15#include "src/gpu/GrOpFlushState.h"
Robert Phillips6941f4a2020-03-12 09:41:54 -040016#include "src/gpu/GrProgramInfo.h"
Jim Van Verth7da048b2019-10-29 13:28:14 -040017#include "src/gpu/GrProxyProvider.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "src/gpu/GrRecordingContextPriv.h"
19#include "src/gpu/effects/GrShadowGeoProc.h"
Robert Phillips3968fcb2019-12-05 16:40:31 -050020#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
Jim Van Verthc5903412016-11-17 15:27:09 -050021
22///////////////////////////////////////////////////////////////////////////////
Jim Van Verth57061ee2017-04-28 17:30:30 -040023// Circle Data
24//
Jim Van Verthc5903412016-11-17 15:27:09 -050025// We have two possible cases for geometry for a circle:
26
27// In the case of a normal fill, we draw geometry for the circle as an octagon.
28static const uint16_t gFillCircleIndices[] = {
Brian Salomonfc527d22016-12-14 21:07:01 -050029 // enter the octagon
30 // clang-format off
31 0, 1, 8, 1, 2, 8,
32 2, 3, 8, 3, 4, 8,
33 4, 5, 8, 5, 6, 8,
34 6, 7, 8, 7, 0, 8,
35 // clang-format on
Jim Van Verthc5903412016-11-17 15:27:09 -050036};
37
38// For stroked circles, we use two nested octagons.
39static const uint16_t gStrokeCircleIndices[] = {
Brian Salomonfc527d22016-12-14 21:07:01 -050040 // enter the octagon
41 // clang-format off
42 0, 1, 9, 0, 9, 8,
43 1, 2, 10, 1, 10, 9,
44 2, 3, 11, 2, 11, 10,
45 3, 4, 12, 3, 12, 11,
46 4, 5, 13, 4, 13, 12,
47 5, 6, 14, 5, 14, 13,
48 6, 7, 15, 6, 15, 14,
49 7, 0, 8, 7, 8, 15,
50 // clang-format on
Jim Van Verthc5903412016-11-17 15:27:09 -050051};
52
53static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
54static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
55static const int kVertsPerStrokeCircle = 16;
56static const int kVertsPerFillCircle = 9;
57
58static int circle_type_to_vert_count(bool stroked) {
59 return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
60}
61
62static int circle_type_to_index_count(bool stroked) {
63 return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
64}
65
66static const uint16_t* circle_type_to_indices(bool stroked) {
67 return stroked ? gStrokeCircleIndices : gFillCircleIndices;
68}
69
70///////////////////////////////////////////////////////////////////////////////
Jim Van Verth57061ee2017-04-28 17:30:30 -040071// RoundRect Data
72//
Jim Van Verthb6069df2017-04-28 11:00:35 -040073// The geometry for a shadow roundrect is similar to a 9-patch:
Jim Van Verthc5903412016-11-17 15:27:09 -050074// ____________
75// |_|________|_|
76// | | | |
77// | | | |
78// | | | |
79// |_|________|_|
80// |_|________|_|
81//
Jim Van Verthb6069df2017-04-28 11:00:35 -040082// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
83// shows the upper part of the upper left corner. The bottom triangle would similarly be split
84// into two triangles.)
85// ________
86// |\ \ |
87// | \ \ |
88// | \\ |
89// | \|
90// --------
91//
92// The center of the fan handles the curve of the corner. For roundrects where the stroke width
93// is greater than the corner radius, the outer triangles blend from the curve to the straight
94// sides. Otherwise these triangles will be degenerate.
95//
96// In the case where the stroke width is greater than the corner radius and the
97// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
98// This rectangle extends the coverage values of the center edges of the 9-patch.
Jim Van Verthc5903412016-11-17 15:27:09 -050099// ____________
100// |_|________|_|
101// | |\ ____ /| |
102// | | | | | |
103// | | |____| | |
104// |_|/______\|_|
105// |_|________|_|
106//
Jim Van Verthb6069df2017-04-28 11:00:35 -0400107// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
Jim Van Verthc5903412016-11-17 15:27:09 -0500108
Jim Van Verthb6069df2017-04-28 11:00:35 -0400109static const uint16_t gRRectIndices[] = {
110 // clang-format off
111 // overstroke quads
112 // we place this at the beginning so that we can skip these indices when rendering as filled
113 0, 6, 25, 0, 25, 24,
114 6, 18, 27, 6, 27, 25,
115 18, 12, 26, 18, 26, 27,
116 12, 0, 24, 12, 24, 26,
Jim Van Verthc5903412016-11-17 15:27:09 -0500117
Jim Van Verthb6069df2017-04-28 11:00:35 -0400118 // corners
119 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
120 6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
121 12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
122 18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
Jim Van Verthc5903412016-11-17 15:27:09 -0500123
Jim Van Verthb6069df2017-04-28 11:00:35 -0400124 // edges
125 0, 5, 11, 0, 11, 6,
126 6, 7, 19, 6, 19, 18,
127 18, 23, 17, 18, 17, 12,
128 12, 13, 1, 12, 1, 0,
129
130 // fill quad
131 // we place this at the end so that we can skip these indices when rendering as stroked
132 0, 6, 18, 0, 18, 12,
133 // clang-format on
Jim Van Verthc5903412016-11-17 15:27:09 -0500134};
Jim Van Verthc5903412016-11-17 15:27:09 -0500135
136// overstroke count
Jim Van Verthb6069df2017-04-28 11:00:35 -0400137static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
Jim Van Verthc5903412016-11-17 15:27:09 -0500138// simple stroke count skips overstroke indices
Jim Van Verthb6069df2017-04-28 11:00:35 -0400139static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6*4;
140// fill count adds final quad to stroke count
141static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
142static const int kVertsPerStrokeRRect = 24;
143static const int kVertsPerOverstrokeRRect = 28;
144static const int kVertsPerFillRRect = 24;
Jim Van Verthc5903412016-11-17 15:27:09 -0500145
146enum RRectType {
147 kFill_RRectType,
148 kStroke_RRectType,
149 kOverstroke_RRectType,
150};
151
152static int rrect_type_to_vert_count(RRectType type) {
Brian Salomonfc527d22016-12-14 21:07:01 -0500153 switch (type) {
154 case kFill_RRectType:
Jim Van Verthb6069df2017-04-28 11:00:35 -0400155 return kVertsPerFillRRect;
Brian Salomonfc527d22016-12-14 21:07:01 -0500156 case kStroke_RRectType:
157 return kVertsPerStrokeRRect;
158 case kOverstroke_RRectType:
159 return kVertsPerOverstrokeRRect;
160 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -0400161 SK_ABORT("Invalid type");
Jim Van Verthc5903412016-11-17 15:27:09 -0500162}
163
164static int rrect_type_to_index_count(RRectType type) {
Brian Salomonfc527d22016-12-14 21:07:01 -0500165 switch (type) {
166 case kFill_RRectType:
Jim Van Verthb6069df2017-04-28 11:00:35 -0400167 return kIndicesPerFillRRect;
Brian Salomonfc527d22016-12-14 21:07:01 -0500168 case kStroke_RRectType:
169 return kIndicesPerStrokeRRect;
170 case kOverstroke_RRectType:
171 return kIndicesPerOverstrokeRRect;
172 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -0400173 SK_ABORT("Invalid type");
Jim Van Verthc5903412016-11-17 15:27:09 -0500174}
175
176static const uint16_t* rrect_type_to_indices(RRectType type) {
Brian Salomonfc527d22016-12-14 21:07:01 -0500177 switch (type) {
178 case kFill_RRectType:
Brian Salomonfc527d22016-12-14 21:07:01 -0500179 case kStroke_RRectType:
Jim Van Verthb6069df2017-04-28 11:00:35 -0400180 return gRRectIndices + 6*4;
Brian Salomonfc527d22016-12-14 21:07:01 -0500181 case kOverstroke_RRectType:
Jim Van Verthb6069df2017-04-28 11:00:35 -0400182 return gRRectIndices;
Brian Salomonfc527d22016-12-14 21:07:01 -0500183 }
Ben Wagnerb4aab9a2017-08-16 10:53:04 -0400184 SK_ABORT("Invalid type");
Jim Van Verthc5903412016-11-17 15:27:09 -0500185}
186
Jim Van Verth57061ee2017-04-28 17:30:30 -0400187///////////////////////////////////////////////////////////////////////////////
Brian Salomon05969092017-07-13 11:20:51 -0400188namespace {
Jim Van Verth57061ee2017-04-28 17:30:30 -0400189
Brian Salomon05969092017-07-13 11:20:51 -0400190class ShadowCircularRRectOp final : public GrMeshDrawOp {
Jim Van Verthc5903412016-11-17 15:27:09 -0500191public:
Brian Salomon25a88092016-12-01 09:36:50 -0500192 DEFINE_OP_CLASS_ID
Jim Van Verthc5903412016-11-17 15:27:09 -0500193
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400194 // An insetWidth > 1/2 rect width or height indicates a simple fill.
Brian Salomon05969092017-07-13 11:20:51 -0400195 ShadowCircularRRectOp(GrColor color, const SkRect& devRect,
Jim Van Verth7da048b2019-10-29 13:28:14 -0400196 float devRadius, bool isCircle, float blurRadius, float insetWidth,
Greg Danielad994cd2019-12-10 09:35:16 -0500197 GrSurfaceProxyView falloffView)
Jim Van Verth7da048b2019-10-29 13:28:14 -0400198 : INHERITED(ClassID())
Greg Danielad994cd2019-12-10 09:35:16 -0500199 , fFalloffView(std::move(falloffView)) {
Jim Van Verthc5903412016-11-17 15:27:09 -0500200 SkRect bounds = devRect;
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400201 SkASSERT(insetWidth > 0);
Jim Van Verth57061ee2017-04-28 17:30:30 -0400202 SkScalar innerRadius = 0.0f;
Jim Van Verthc5903412016-11-17 15:27:09 -0500203 SkScalar outerRadius = devRadius;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400204 SkScalar umbraInset;
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400205
206 RRectType type = kFill_RRectType;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400207 if (isCircle) {
208 umbraInset = 0;
209 } else {
Brian Osman788b9162020-02-07 10:36:46 -0500210 umbraInset = std::max(outerRadius, blurRadius);
Jim Van Verth57061ee2017-04-28 17:30:30 -0400211 }
212
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400213 // If stroke is greater than width or height, this is still a fill,
214 // otherwise we compute stroke params.
215 if (isCircle) {
216 innerRadius = devRadius - insetWidth;
217 type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
218 } else {
Brian Osman788b9162020-02-07 10:36:46 -0500219 if (insetWidth <= 0.5f*std::min(devRect.width(), devRect.height())) {
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400220 // We don't worry about a real inner radius, we just need to know if we
221 // need to create overstroke vertices.
Brian Osman788b9162020-02-07 10:36:46 -0500222 innerRadius = std::max(insetWidth - umbraInset, 0.0f);
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400223 type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
Jim Van Verthb6069df2017-04-28 11:00:35 -0400224 }
Jim Van Verthc5903412016-11-17 15:27:09 -0500225 }
226
Greg Daniel5faf4742019-10-01 15:14:44 -0400227 this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo);
Jim Van Verthc5903412016-11-17 15:27:09 -0500228
Jim Van Verth57061ee2017-04-28 17:30:30 -0400229 fGeoData.emplace_back(Geometry{color, outerRadius, umbraInset, innerRadius,
Jim Van Verthfb186392018-09-11 11:37:46 -0400230 blurRadius, bounds, type, isCircle});
Jim Van Verth57061ee2017-04-28 17:30:30 -0400231 if (isCircle) {
232 fVertCount = circle_type_to_vert_count(kStroke_RRectType == type);
233 fIndexCount = circle_type_to_index_count(kStroke_RRectType == type);
234 } else {
235 fVertCount = rrect_type_to_vert_count(type);
236 fIndexCount = rrect_type_to_index_count(type);
237 }
Jim Van Verthc5903412016-11-17 15:27:09 -0500238 }
239
Brian Salomonfc527d22016-12-14 21:07:01 -0500240 const char* name() const override { return "ShadowCircularRRectOp"; }
Jim Van Verthc5903412016-11-17 15:27:09 -0500241
Brian Osman9a390ac2018-11-12 09:47:48 -0500242#ifdef SK_DEBUG
Jim Van Verthc5903412016-11-17 15:27:09 -0500243 SkString dumpInfo() const override {
244 SkString string;
245 for (int i = 0; i < fGeoData.count(); ++i) {
Brian Salomonfc527d22016-12-14 21:07:01 -0500246 string.appendf(
247 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
Jim Van Verth57061ee2017-04-28 17:30:30 -0400248 "OuterRad: %.2f, Umbra: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
Brian Salomonfc527d22016-12-14 21:07:01 -0500249 fGeoData[i].fColor, fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
250 fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
Jim Van Verthb6069df2017-04-28 11:00:35 -0400251 fGeoData[i].fOuterRadius, fGeoData[i].fUmbraInset,
Jim Van Verth57061ee2017-04-28 17:30:30 -0400252 fGeoData[i].fInnerRadius, fGeoData[i].fBlurRadius);
Jim Van Verthc5903412016-11-17 15:27:09 -0500253 }
Jim Van Verthc5903412016-11-17 15:27:09 -0500254 string.append(INHERITED::dumpInfo());
255 return string;
256 }
Brian Osman9a390ac2018-11-12 09:47:48 -0500257#endif
Jim Van Verthc5903412016-11-17 15:27:09 -0500258
Brian Salomon05969092017-07-13 11:20:51 -0400259 FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
260
Chris Dalton6ce447a2019-06-23 18:07:38 -0600261 GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
262 bool hasMixedSampledCoverage, GrClampType) override {
Chris Dalton4b62aed2019-01-15 11:53:00 -0700263 return GrProcessorSet::EmptySetAnalysis();
Brian Salomon05969092017-07-13 11:20:51 -0400264 }
265
Brian Salomon92aee3d2016-12-21 09:20:25 -0500266private:
Jim Van Verth57061ee2017-04-28 17:30:30 -0400267 struct Geometry {
268 GrColor fColor;
269 SkScalar fOuterRadius;
270 SkScalar fUmbraInset;
271 SkScalar fInnerRadius;
272 SkScalar fBlurRadius;
273 SkRect fDevBounds;
274 RRectType fType;
275 bool fIsCircle;
276 };
277
Jim Van Verthc5903412016-11-17 15:27:09 -0500278 struct CircleVertex {
Brian Salomonfc527d22016-12-14 21:07:01 -0500279 SkPoint fPos;
280 GrColor fColor;
281 SkPoint fOffset;
Jim Van Verthb6069df2017-04-28 11:00:35 -0400282 SkScalar fDistanceCorrection;
Jim Van Verthc5903412016-11-17 15:27:09 -0500283 };
284
Jim Van Verth57061ee2017-04-28 17:30:30 -0400285 void fillInCircleVerts(const Geometry& args, bool isStroked, CircleVertex** verts) const {
286
287 GrColor color = args.fColor;
288 SkScalar outerRadius = args.fOuterRadius;
289 SkScalar innerRadius = args.fInnerRadius;
290 SkScalar blurRadius = args.fBlurRadius;
291 SkScalar distanceCorrection = outerRadius / blurRadius;
292
293 const SkRect& bounds = args.fDevBounds;
294
295 // The inner radius in the vertex data must be specified in normalized space.
296 innerRadius = innerRadius / outerRadius;
297
298 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
299 SkScalar halfWidth = 0.5f * bounds.width();
300 SkScalar octOffset = 0.41421356237f; // sqrt(2) - 1
301
302 (*verts)->fPos = center + SkPoint::Make(-octOffset * halfWidth, -halfWidth);
Jim Van Verthc5903412016-11-17 15:27:09 -0500303 (*verts)->fColor = color;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400304 (*verts)->fOffset = SkPoint::Make(-octOffset, -1);
Jim Van Verthb6069df2017-04-28 11:00:35 -0400305 (*verts)->fDistanceCorrection = distanceCorrection;
Jim Van Verthc5903412016-11-17 15:27:09 -0500306 (*verts)++;
307
Jim Van Verth57061ee2017-04-28 17:30:30 -0400308 (*verts)->fPos = center + SkPoint::Make(octOffset * halfWidth, -halfWidth);
Jim Van Verthc5903412016-11-17 15:27:09 -0500309 (*verts)->fColor = color;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400310 (*verts)->fOffset = SkPoint::Make(octOffset, -1);
Jim Van Verthb6069df2017-04-28 11:00:35 -0400311 (*verts)->fDistanceCorrection = distanceCorrection;
Jim Van Verthc5903412016-11-17 15:27:09 -0500312 (*verts)++;
313
Jim Van Verth57061ee2017-04-28 17:30:30 -0400314 (*verts)->fPos = center + SkPoint::Make(halfWidth, -octOffset * halfWidth);
Jim Van Verthc5903412016-11-17 15:27:09 -0500315 (*verts)->fColor = color;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400316 (*verts)->fOffset = SkPoint::Make(1, -octOffset);
Jim Van Verthb6069df2017-04-28 11:00:35 -0400317 (*verts)->fDistanceCorrection = distanceCorrection;
Jim Van Verthc5903412016-11-17 15:27:09 -0500318 (*verts)++;
319
Jim Van Verth57061ee2017-04-28 17:30:30 -0400320 (*verts)->fPos = center + SkPoint::Make(halfWidth, octOffset * halfWidth);
Jim Van Verthc5903412016-11-17 15:27:09 -0500321 (*verts)->fColor = color;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400322 (*verts)->fOffset = SkPoint::Make(1, octOffset);
Jim Van Verthb6069df2017-04-28 11:00:35 -0400323 (*verts)->fDistanceCorrection = distanceCorrection;
Jim Van Verthc5903412016-11-17 15:27:09 -0500324 (*verts)++;
Jim Van Verth57061ee2017-04-28 17:30:30 -0400325
326 (*verts)->fPos = center + SkPoint::Make(octOffset * halfWidth, halfWidth);
327 (*verts)->fColor = color;
328 (*verts)->fOffset = SkPoint::Make(octOffset, 1);
329 (*verts)->fDistanceCorrection = distanceCorrection;
330 (*verts)++;
331
332 (*verts)->fPos = center + SkPoint::Make(-octOffset * halfWidth, halfWidth);
333 (*verts)->fColor = color;
334 (*verts)->fOffset = SkPoint::Make(-octOffset, 1);
335 (*verts)->fDistanceCorrection = distanceCorrection;
336 (*verts)++;
337
338 (*verts)->fPos = center + SkPoint::Make(-halfWidth, octOffset * halfWidth);
339 (*verts)->fColor = color;
340 (*verts)->fOffset = SkPoint::Make(-1, octOffset);
341 (*verts)->fDistanceCorrection = distanceCorrection;
342 (*verts)++;
343
344 (*verts)->fPos = center + SkPoint::Make(-halfWidth, -octOffset * halfWidth);
345 (*verts)->fColor = color;
346 (*verts)->fOffset = SkPoint::Make(-1, -octOffset);
347 (*verts)->fDistanceCorrection = distanceCorrection;
348 (*verts)++;
349
350 if (isStroked) {
351 // compute the inner ring
352
353 // cosine and sine of pi/8
354 SkScalar c = 0.923579533f;
355 SkScalar s = 0.382683432f;
356 SkScalar r = args.fInnerRadius;
357
358 (*verts)->fPos = center + SkPoint::Make(-s * r, -c * r);
359 (*verts)->fColor = color;
360 (*verts)->fOffset = SkPoint::Make(-s * innerRadius, -c * innerRadius);
361 (*verts)->fDistanceCorrection = distanceCorrection;
362 (*verts)++;
363
364 (*verts)->fPos = center + SkPoint::Make(s * r, -c * r);
365 (*verts)->fColor = color;
366 (*verts)->fOffset = SkPoint::Make(s * innerRadius, -c * innerRadius);
367 (*verts)->fDistanceCorrection = distanceCorrection;
368 (*verts)++;
369
370 (*verts)->fPos = center + SkPoint::Make(c * r, -s * r);
371 (*verts)->fColor = color;
372 (*verts)->fOffset = SkPoint::Make(c * innerRadius, -s * innerRadius);
373 (*verts)->fDistanceCorrection = distanceCorrection;
374 (*verts)++;
375
376 (*verts)->fPos = center + SkPoint::Make(c * r, s * r);
377 (*verts)->fColor = color;
378 (*verts)->fOffset = SkPoint::Make(c * innerRadius, s * innerRadius);
379 (*verts)->fDistanceCorrection = distanceCorrection;
380 (*verts)++;
381
382 (*verts)->fPos = center + SkPoint::Make(s * r, c * r);
383 (*verts)->fColor = color;
384 (*verts)->fOffset = SkPoint::Make(s * innerRadius, c * innerRadius);
385 (*verts)->fDistanceCorrection = distanceCorrection;
386 (*verts)++;
387
388 (*verts)->fPos = center + SkPoint::Make(-s * r, c * r);
389 (*verts)->fColor = color;
390 (*verts)->fOffset = SkPoint::Make(-s * innerRadius, c * innerRadius);
391 (*verts)->fDistanceCorrection = distanceCorrection;
392 (*verts)++;
393
394 (*verts)->fPos = center + SkPoint::Make(-c * r, s * r);
395 (*verts)->fColor = color;
396 (*verts)->fOffset = SkPoint::Make(-c * innerRadius, s * innerRadius);
397 (*verts)->fDistanceCorrection = distanceCorrection;
398 (*verts)++;
399
400 (*verts)->fPos = center + SkPoint::Make(-c * r, -s * r);
401 (*verts)->fColor = color;
402 (*verts)->fOffset = SkPoint::Make(-c * innerRadius, -s * innerRadius);
403 (*verts)->fDistanceCorrection = distanceCorrection;
404 (*verts)++;
405 } else {
406 // filled
407 (*verts)->fPos = center;
408 (*verts)->fColor = color;
409 (*verts)->fOffset = SkPoint::Make(0, 0);
410 (*verts)->fDistanceCorrection = distanceCorrection;
411 (*verts)++;
412 }
413 }
414
415 void fillInRRectVerts(const Geometry& args, CircleVertex** verts) const {
416 GrColor color = args.fColor;
417 SkScalar outerRadius = args.fOuterRadius;
418
419 const SkRect& bounds = args.fDevBounds;
420
421 SkScalar umbraInset = args.fUmbraInset;
Brian Osman788b9162020-02-07 10:36:46 -0500422 SkScalar minDim = 0.5f*std::min(bounds.width(), bounds.height());
Jim Van Verth57061ee2017-04-28 17:30:30 -0400423 if (umbraInset > minDim) {
424 umbraInset = minDim;
425 }
426
427 SkScalar xInner[4] = { bounds.fLeft + umbraInset, bounds.fRight - umbraInset,
428 bounds.fLeft + umbraInset, bounds.fRight - umbraInset };
429 SkScalar xMid[4] = { bounds.fLeft + outerRadius, bounds.fRight - outerRadius,
430 bounds.fLeft + outerRadius, bounds.fRight - outerRadius };
431 SkScalar xOuter[4] = { bounds.fLeft, bounds.fRight,
432 bounds.fLeft, bounds.fRight };
433 SkScalar yInner[4] = { bounds.fTop + umbraInset, bounds.fTop + umbraInset,
434 bounds.fBottom - umbraInset, bounds.fBottom - umbraInset };
435 SkScalar yMid[4] = { bounds.fTop + outerRadius, bounds.fTop + outerRadius,
436 bounds.fBottom - outerRadius, bounds.fBottom - outerRadius };
437 SkScalar yOuter[4] = { bounds.fTop, bounds.fTop,
438 bounds.fBottom, bounds.fBottom };
439
440 SkScalar blurRadius = args.fBlurRadius;
441
442 // In the case where we have to inset more for the umbra, our two triangles in the
443 // corner get skewed to a diamond rather than a square. To correct for that,
444 // we also skew the vectors we send to the shader that help define the circle.
445 // By doing so, we end up with a quarter circle in the corner rather than the
446 // elliptical curve.
Jim Van Verth4c8c1e82018-04-23 17:14:48 -0400447
448 // This is a bit magical, but it gives us the correct results at extrema:
449 // a) umbraInset == outerRadius produces an orthogonal vector
450 // b) outerRadius == 0 produces a diagonal vector
451 // And visually the corner looks correct.
452 SkVector outerVec = SkVector::Make(outerRadius - umbraInset, -outerRadius - umbraInset);
Jim Van Verth57061ee2017-04-28 17:30:30 -0400453 outerVec.normalize();
Jim Van Verth4c8c1e82018-04-23 17:14:48 -0400454 // We want the circle edge to fall fractionally along the diagonal at
455 // (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
456 //
457 // Setting the components of the diagonal offset to the following value will give us that.
458 SkScalar diagVal = umbraInset / (SK_ScalarSqrt2*(outerRadius - umbraInset) - outerRadius);
459 SkVector diagVec = SkVector::Make(diagVal, diagVal);
Jim Van Verth57061ee2017-04-28 17:30:30 -0400460 SkScalar distanceCorrection = umbraInset / blurRadius;
461
462 // build corner by corner
463 for (int i = 0; i < 4; ++i) {
464 // inner point
465 (*verts)->fPos = SkPoint::Make(xInner[i], yInner[i]);
466 (*verts)->fColor = color;
467 (*verts)->fOffset = SkVector::Make(0, 0);
468 (*verts)->fDistanceCorrection = distanceCorrection;
469 (*verts)++;
470
471 // outer points
472 (*verts)->fPos = SkPoint::Make(xOuter[i], yInner[i]);
473 (*verts)->fColor = color;
474 (*verts)->fOffset = SkVector::Make(0, -1);
475 (*verts)->fDistanceCorrection = distanceCorrection;
476 (*verts)++;
477
478 (*verts)->fPos = SkPoint::Make(xOuter[i], yMid[i]);
479 (*verts)->fColor = color;
480 (*verts)->fOffset = outerVec;
481 (*verts)->fDistanceCorrection = distanceCorrection;
482 (*verts)++;
483
484 (*verts)->fPos = SkPoint::Make(xOuter[i], yOuter[i]);
485 (*verts)->fColor = color;
486 (*verts)->fOffset = diagVec;
487 (*verts)->fDistanceCorrection = distanceCorrection;
488 (*verts)++;
489
490 (*verts)->fPos = SkPoint::Make(xMid[i], yOuter[i]);
491 (*verts)->fColor = color;
492 (*verts)->fOffset = outerVec;
493 (*verts)->fDistanceCorrection = distanceCorrection;
494 (*verts)++;
495
496 (*verts)->fPos = SkPoint::Make(xInner[i], yOuter[i]);
497 (*verts)->fColor = color;
498 (*verts)->fOffset = SkVector::Make(0, -1);
499 (*verts)->fDistanceCorrection = distanceCorrection;
500 (*verts)++;
501 }
502
503 // Add the additional vertices for overstroked rrects.
504 // Effectively this is an additional stroked rrect, with its
505 // parameters equal to those in the center of the 9-patch. This will
506 // give constant values across this inner ring.
507 if (kOverstroke_RRectType == args.fType) {
508 SkASSERT(args.fInnerRadius > 0.0f);
509
510 SkScalar inset = umbraInset + args.fInnerRadius;
511
512 // TL
513 (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fTop + inset);
514 (*verts)->fColor = color;
515 (*verts)->fOffset = SkPoint::Make(0, 0);
516 (*verts)->fDistanceCorrection = distanceCorrection;
517 (*verts)++;
518
519 // TR
520 (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fTop + inset);
521 (*verts)->fColor = color;
522 (*verts)->fOffset = SkPoint::Make(0, 0);
523 (*verts)->fDistanceCorrection = distanceCorrection;
524 (*verts)++;
525
526 // BL
527 (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fBottom - inset);
528 (*verts)->fColor = color;
529 (*verts)->fOffset = SkPoint::Make(0, 0);
530 (*verts)->fDistanceCorrection = distanceCorrection;
531 (*verts)++;
532
533 // BR
534 (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fBottom - inset);
535 (*verts)->fColor = color;
536 (*verts)->fOffset = SkPoint::Make(0, 0);
537 (*verts)->fDistanceCorrection = distanceCorrection;
538 (*verts)++;
539 }
540
Jim Van Verthc5903412016-11-17 15:27:09 -0500541 }
542
Robert Phillips6941f4a2020-03-12 09:41:54 -0400543 void onCreateProgramInfo(const GrCaps* caps,
544 SkArenaAlloc* arena,
Robert Phillips4133dc42020-03-11 15:55:55 -0400545 const GrSurfaceProxyView* outputView,
Robert Phillips6941f4a2020-03-12 09:41:54 -0400546 GrAppliedClip&& appliedClip,
547 const GrXferProcessor::DstProxyView& dstProxyView) override {
548 GrGeometryProcessor* gp = GrRRectShadowGeoProc::Make(arena, fFalloffView);
549 SkASSERT(sizeof(CircleVertex) == gp->vertexStride());
550
551 static constexpr int kOnePrimProcTexture = 1;
552 auto fixedDynamicState = GrMeshDrawOp::Target::MakeFixedDynamicState(arena, &appliedClip,
553 kOnePrimProcTexture);
554 fixedDynamicState->fPrimitiveProcessorTextures[0] = fFalloffView.proxy();
555
556 fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo(caps, arena, outputView,
557 std::move(appliedClip),
558 dstProxyView, gp,
559 GrProcessorSet::MakeEmptySet(),
560 GrPrimitiveType::kTriangles,
561 GrPipeline::InputFlags::kNone,
562 &GrUserStencilSettings::kUnused,
563 fixedDynamicState);
564 }
565
566 void onPrePrepareDraws(GrRecordingContext* context,
567 const GrSurfaceProxyView* outputView,
568 GrAppliedClip* clip,
569 const GrXferProcessor::DstProxyView& dstProxyView) override {
570 SkArenaAlloc* arena = context->priv().recordTimeAllocator();
571
572 // This is equivalent to a GrOpFlushState::detachAppliedClip
573 GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip();
574
575 this->createProgramInfo(context->priv().caps(), arena, outputView,
576 std::move(appliedClip), dstProxyView);
577
578 context->priv().recordProgramInfo(fProgramInfo);
Robert Phillips4133dc42020-03-11 15:55:55 -0400579 }
580
Brian Salomon91326c32017-08-09 16:02:19 -0400581 void onPrepareDraws(Target* target) override {
Jim Van Verthc5903412016-11-17 15:27:09 -0500582 int instanceCount = fGeoData.count();
Jim Van Verthc5903412016-11-17 15:27:09 -0500583
Brian Salomon12d22642019-01-29 14:38:50 -0500584 sk_sp<const GrBuffer> vertexBuffer;
Jim Van Verthc5903412016-11-17 15:27:09 -0500585 int firstVertex;
Brian Salomon92be2f72018-06-19 14:33:47 -0400586 CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(
587 sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex);
Jim Van Verthc5903412016-11-17 15:27:09 -0500588 if (!verts) {
589 SkDebugf("Could not allocate vertices\n");
590 return;
591 }
592
Brian Salomon12d22642019-01-29 14:38:50 -0500593 sk_sp<const GrBuffer> indexBuffer;
Jim Van Verthc5903412016-11-17 15:27:09 -0500594 int firstIndex = 0;
595 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
596 if (!indices) {
597 SkDebugf("Could not allocate indices\n");
598 return;
599 }
600
601 int currStartVertex = 0;
602 for (int i = 0; i < instanceCount; i++) {
603 const Geometry& args = fGeoData[i];
604
Jim Van Verth57061ee2017-04-28 17:30:30 -0400605 if (args.fIsCircle) {
606 bool isStroked = SkToBool(kStroke_RRectType == args.fType);
607 this->fillInCircleVerts(args, isStroked, &verts);
Jim Van Verthc5903412016-11-17 15:27:09 -0500608
Jim Van Verth57061ee2017-04-28 17:30:30 -0400609 const uint16_t* primIndices = circle_type_to_indices(isStroked);
610 const int primIndexCount = circle_type_to_index_count(isStroked);
611 for (int i = 0; i < primIndexCount; ++i) {
612 *indices++ = primIndices[i] + currStartVertex;
613 }
Jim Van Verthc5903412016-11-17 15:27:09 -0500614
Jim Van Verth57061ee2017-04-28 17:30:30 -0400615 currStartVertex += circle_type_to_vert_count(isStroked);
Jim Van Verthc5903412016-11-17 15:27:09 -0500616
Jim Van Verth57061ee2017-04-28 17:30:30 -0400617 } else {
618 this->fillInRRectVerts(args, &verts);
619
620 const uint16_t* primIndices = rrect_type_to_indices(args.fType);
621 const int primIndexCount = rrect_type_to_index_count(args.fType);
622 for (int i = 0; i < primIndexCount; ++i) {
623 *indices++ = primIndices[i] + currStartVertex;
624 }
625
626 currStartVertex += rrect_type_to_vert_count(args.fType);
Jim Van Verthb6069df2017-04-28 11:00:35 -0400627 }
Jim Van Verthc5903412016-11-17 15:27:09 -0500628 }
629
Robert Phillips6941f4a2020-03-12 09:41:54 -0400630 fMesh = target->allocMesh();
631 fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
632 GrPrimitiveRestart::kNo);
633 fMesh->setVertexData(std::move(vertexBuffer), firstVertex);
Chris Dalton07cdcfc92019-02-26 11:13:22 -0700634 }
635
636 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
Robert Phillips6941f4a2020-03-12 09:41:54 -0400637 if (!fProgramInfo) {
638 this->createProgramInfo(flushState);
639 }
Robert Phillips3968fcb2019-12-05 16:40:31 -0500640
Robert Phillips6941f4a2020-03-12 09:41:54 -0400641 if (!fProgramInfo || !fMesh) {
642 return;
643 }
644
645 flushState->opsRenderPass()->bindPipeline(*fProgramInfo, chainBounds);
646 flushState->opsRenderPass()->drawMeshes(*fProgramInfo, fMesh, 1);
Jim Van Verthc5903412016-11-17 15:27:09 -0500647 }
648
Michael Ludwig28b0c5d2019-12-19 14:51:00 -0500649 CombineResult onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*,
650 const GrCaps& caps) override {
Brian Salomonfc527d22016-12-14 21:07:01 -0500651 ShadowCircularRRectOp* that = t->cast<ShadowCircularRRectOp>();
Jim Van Verthc5903412016-11-17 15:27:09 -0500652 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
Jim Van Verthc5903412016-11-17 15:27:09 -0500653 fVertCount += that->fVertCount;
654 fIndexCount += that->fIndexCount;
Brian Salomon7eae3e02018-08-07 14:02:38 +0000655 return CombineResult::kMerged;
Jim Van Verthc5903412016-11-17 15:27:09 -0500656 }
657
Jim Van Verth7da048b2019-10-29 13:28:14 -0400658 void visitProxies(const VisitProxyFunc& func) const override {
Robert Phillips6941f4a2020-03-12 09:41:54 -0400659 if (fProgramInfo) {
660 fProgramInfo->visitProxies(func);
661 } else {
662 func(fFalloffView.proxy(), GrMipMapped(false));
663 }
Jim Van Verth7da048b2019-10-29 13:28:14 -0400664 }
665
Jim Van Verthc5903412016-11-17 15:27:09 -0500666 SkSTArray<1, Geometry, true> fGeoData;
Brian Salomonfc527d22016-12-14 21:07:01 -0500667 int fVertCount;
668 int fIndexCount;
Greg Danielad994cd2019-12-10 09:35:16 -0500669 GrSurfaceProxyView fFalloffView;
Jim Van Verthc5903412016-11-17 15:27:09 -0500670
Robert Phillips6941f4a2020-03-12 09:41:54 -0400671 GrMesh* fMesh = nullptr;
672 GrProgramInfo* fProgramInfo = nullptr;
673
Brian Salomon05969092017-07-13 11:20:51 -0400674 typedef GrMeshDrawOp INHERITED;
Jim Van Verthc5903412016-11-17 15:27:09 -0500675};
676
Brian Salomon05969092017-07-13 11:20:51 -0400677} // anonymous namespace
678
Jim Van Verthc5903412016-11-17 15:27:09 -0500679///////////////////////////////////////////////////////////////////////////////
680
Jim Van Verth57061ee2017-04-28 17:30:30 -0400681namespace GrShadowRRectOp {
Jim Van Verth7da048b2019-10-29 13:28:14 -0400682
Greg Daniel9f0dfbd2020-02-10 11:47:11 -0500683static GrSurfaceProxyView create_falloff_texture(GrRecordingContext* context) {
Jim Van Verth7da048b2019-10-29 13:28:14 -0400684 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
685 GrUniqueKey key;
686 GrUniqueKey::Builder builder(&key, kDomain, 0, "Shadow Gaussian Falloff");
687 builder.finish();
688
Greg Daniel6f5441a2020-01-28 17:02:49 -0500689 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
690
Greg Daniel3a365112020-02-14 10:47:18 -0500691 if (sk_sp<GrTextureProxy> falloffTexture =
692 proxyProvider->findOrCreateProxyByUniqueKey(key, GrColorType::kAlpha_8)) {
Greg Daniel9f0dfbd2020-02-10 11:47:11 -0500693 GrSwizzle swizzle = context->priv().caps()->getReadSwizzle(falloffTexture->backendFormat(),
694 GrColorType::kAlpha_8);
695 return {std::move(falloffTexture), kTopLeft_GrSurfaceOrigin, swizzle};
Jim Van Verth7da048b2019-10-29 13:28:14 -0400696 }
697
Greg Daniel9f0dfbd2020-02-10 11:47:11 -0500698 static const int kWidth = 128;
699 static const size_t kRowBytes = kWidth * GrColorTypeBytesPerPixel(GrColorType::kAlpha_8);
700 SkImageInfo ii = SkImageInfo::MakeA8(kWidth, 1);
701
702 SkBitmap bitmap;
703 bitmap.allocPixels(ii, kRowBytes);
704
705 unsigned char* values = (unsigned char*)bitmap.getPixels();
706 for (int i = 0; i < 128; ++i) {
707 SkScalar d = SK_Scalar1 - i / SkIntToScalar(127);
708 values[i] = SkScalarRoundToInt((SkScalarExp(-4 * d * d) - 0.018f) * 255);
709 }
710 bitmap.setImmutable();
711
712 GrBitmapTextureMaker maker(context, bitmap);
Brian Salomonecbb0fb2020-02-28 18:07:32 -0500713 auto view = maker.view(GrMipMapped::kNo);
Greg Daniel9f0dfbd2020-02-10 11:47:11 -0500714 SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
715
716 if (view) {
717 proxyProvider->assignUniqueKeyToProxy(key, view.asTextureProxy());
718 }
719 return view;
Jim Van Verth7da048b2019-10-29 13:28:14 -0400720}
721
722
Robert Phillipsb97da532019-02-12 15:24:12 -0500723std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
Robert Phillips7c525e62018-06-12 10:11:12 -0400724 GrColor color,
Brian Salomon05969092017-07-13 11:20:51 -0400725 const SkMatrix& viewMatrix,
726 const SkRRect& rrect,
727 SkScalar blurWidth,
Jim Van Verthfb186392018-09-11 11:37:46 -0400728 SkScalar insetWidth) {
Brian Salomon53e4c3c2016-12-21 11:38:53 -0500729 // Shadow rrect ops only handle simple circular rrects.
Mike Reed242135a2018-02-22 13:41:39 -0500730 SkASSERT(viewMatrix.isSimilarity() && SkRRectPriv::EqualRadii(rrect));
Jim Van Verth57061ee2017-04-28 17:30:30 -0400731
Greg Daniel9f0dfbd2020-02-10 11:47:11 -0500732 GrSurfaceProxyView falloffView = create_falloff_texture(context);
733 if (!falloffView) {
Jim Van Verth7da048b2019-10-29 13:28:14 -0400734 return nullptr;
735 }
736
Brian Salomon53e4c3c2016-12-21 11:38:53 -0500737 // Do any matrix crunching before we reset the draw state for device coords.
Jim Van Verthc5903412016-11-17 15:27:09 -0500738 const SkRect& rrectBounds = rrect.getBounds();
739 SkRect bounds;
740 viewMatrix.mapRect(&bounds, rrectBounds);
741
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400742 // Map radius and inset. As the matrix is a similarity matrix, this should be isotropic.
Mike Reed242135a2018-02-22 13:41:39 -0500743 SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
Jim Van Verth8d1e0ac2017-05-05 15:53:23 -0400744 SkScalar matrixFactor = viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewX];
745 SkScalar scaledRadius = SkScalarAbs(radius*matrixFactor);
746 SkScalar scaledInsetWidth = SkScalarAbs(insetWidth*matrixFactor);
Jim Van Verthc5903412016-11-17 15:27:09 -0500747
Robert Phillipse5763782019-04-17 14:38:24 -0400748 if (scaledInsetWidth <= 0) {
749 return nullptr;
750 }
751
Robert Phillips9da87e02019-02-04 13:26:26 -0500752 GrOpMemoryPool* pool = context->priv().opMemoryPool();
Robert Phillipsc994a932018-06-19 13:09:54 -0400753
754 return pool->allocate<ShadowCircularRRectOp>(color, bounds,
755 scaledRadius,
756 rrect.isOval(),
757 blurWidth,
Jim Van Verth7da048b2019-10-29 13:28:14 -0400758 scaledInsetWidth,
Greg Danielad994cd2019-12-10 09:35:16 -0500759 std::move(falloffView));
Jim Van Verthc5903412016-11-17 15:27:09 -0500760}
Brian Salomonfc527d22016-12-14 21:07:01 -0500761}
Jim Van Verth57061ee2017-04-28 17:30:30 -0400762
Jim Van Verthc5903412016-11-17 15:27:09 -0500763///////////////////////////////////////////////////////////////////////////////
764
Hal Canary6f6961e2017-01-31 13:50:44 -0500765#if GR_TEST_UTILS
Jim Van Verthc5903412016-11-17 15:27:09 -0500766
Brian Salomon05969092017-07-13 11:20:51 -0400767GR_DRAW_OP_TEST_DEFINE(ShadowRRectOp) {
Brian Salomonfc118442019-11-22 19:09:27 -0500768 // We may choose matrix and inset values that cause the factory to fail. We loop until we find
769 // an acceptable combination.
Brian Osman4462c042018-06-08 16:35:44 -0400770 do {
Brian Salomonfc118442019-11-22 19:09:27 -0500771 // create a similarity matrix
772 SkScalar rotate = random->nextSScalar1() * 360.f;
773 SkScalar translateX = random->nextSScalar1() * 1000.f;
774 SkScalar translateY = random->nextSScalar1() * 1000.f;
775 SkScalar scale = random->nextSScalar1() * 100.f;
776 SkMatrix viewMatrix;
777 viewMatrix.setRotate(rotate);
778 viewMatrix.postTranslate(translateX, translateY);
779 viewMatrix.postScale(scale, scale);
780 SkScalar insetWidth = random->nextSScalar1() * 72.f;
781 SkScalar blurWidth = random->nextSScalar1() * 72.f;
782 bool isCircle = random->nextBool();
783 // This op doesn't use a full GrPaint, just a color.
784 GrColor color = paint.getColor4f().toBytes_RGBA();
785 if (isCircle) {
786 SkRect circle = GrTest::TestSquare(random);
787 SkRRect rrect = SkRRect::MakeOval(circle);
788 if (auto op = GrShadowRRectOp::Make(
789 context, color, viewMatrix, rrect, blurWidth, insetWidth)) {
790 return op;
791 }
792 } else {
793 SkRRect rrect;
794 do {
795 // This may return a rrect with elliptical corners, which will cause an assert.
796 rrect = GrTest::TestRRectSimple(random);
797 } while (!SkRRectPriv::IsSimpleCircular(rrect));
798 if (auto op = GrShadowRRectOp::Make(
799 context, color, viewMatrix, rrect, blurWidth, insetWidth)) {
800 return op;
801 }
802 }
803 } while (true);
Jim Van Verthc5903412016-11-17 15:27:09 -0500804}
805
806#endif