245 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * Copyright 2018 Google Inc.
 | |
|  *
 | |
|  * Use of this source code is governed by a BSD-style license that can be
 | |
|  * found in the LICENSE file.
 | |
|  */
 | |
| 
 | |
| #include "gm/gm.h"
 | |
| #include "include/core/SkCanvas.h"
 | |
| #include "include/core/SkColor.h"
 | |
| #include "include/core/SkMatrix.h"
 | |
| #include "include/core/SkPaint.h"
 | |
| #include "include/core/SkPath.h"
 | |
| #include "include/core/SkPoint.h"
 | |
| #include "include/core/SkRect.h"
 | |
| #include "include/core/SkSize.h"
 | |
| #include "include/core/SkString.h"
 | |
| #include "include/core/SkTypes.h"
 | |
| #include "include/gpu/GrContextOptions.h"
 | |
| #include "include/gpu/GrDirectContext.h"
 | |
| #include "include/utils/SkRandom.h"
 | |
| #include "src/core/SkGeometry.h"
 | |
| #include "src/gpu/GrCaps.h"
 | |
| #include "src/gpu/GrDirectContextPriv.h"
 | |
| #include "src/gpu/GrDrawingManager.h"
 | |
| #include "src/gpu/GrRecordingContextPriv.h"
 | |
| 
 | |
| static constexpr float kStrokeWidth = 30;
 | |
| static constexpr int kCellSize = 200;
 | |
| static constexpr int kNumCols = 5;
 | |
| static constexpr int kNumRows = 5;
 | |
| static constexpr int kTestWidth = kNumCols * kCellSize;
 | |
| static constexpr int kTestHeight = kNumRows * kCellSize;
 | |
| 
 | |
| enum class CellFillMode {
 | |
|     kStretch,
 | |
|     kCenter
 | |
| };
 | |
| 
 | |
| struct TrickyCubic {
 | |
|     SkPoint fPoints[4];
 | |
|     int fNumPts;
 | |
|     CellFillMode fFillMode;
 | |
|     float fScale = 1;
 | |
| };
 | |
| 
 | |
| // This is a compilation of cubics that have given strokers grief. Feel free to add more.
 | |
| static const TrickyCubic kTrickyCubics[] = {
 | |
|     {{{122, 737}, {348, 553}, {403, 761}, {400, 760}}, 4, CellFillMode::kStretch},
 | |
|     {{{244, 520}, {244, 518}, {1141, 634}, {394, 688}}, 4, CellFillMode::kStretch},
 | |
|     {{{550, 194}, {138, 130}, {1035, 246}, {288, 300}}, 4, CellFillMode::kStretch},
 | |
|     {{{226, 733}, {556, 779}, {-43, 471}, {348, 683}}, 4, CellFillMode::kStretch},
 | |
|     {{{268, 204}, {492, 304}, {352, 23}, {433, 412}}, 4, CellFillMode::kStretch},
 | |
|     {{{172, 480}, {396, 580}, {256, 299}, {338, 677}}, 4, CellFillMode::kStretch},
 | |
|     {{{731, 340}, {318, 252}, {1026, -64}, {367, 265}}, 4, CellFillMode::kStretch},
 | |
|     {{{475, 708}, {62, 620}, {770, 304}, {220, 659}}, 4, CellFillMode::kStretch},
 | |
|     {{{0, 0}, {128, 128}, {128, 0}, {0, 128}}, 4, CellFillMode::kCenter},  // Perfect cusp
 | |
|     {{{0,.01f}, {128,127.999f}, {128,.01f}, {0,127.99f}}, 4, CellFillMode::kCenter},  // Near-cusp
 | |
|     {{{0,-.01f}, {128,128.001f}, {128,-.01f}, {0,128.001f}}, 4, CellFillMode::kCenter}, // Near-cusp
 | |
|     {{{0,0}, {0,-10}, {0,-10}, {0,10}}, 4, CellFillMode::kCenter, 1.098283f},  // Flat line with 180
 | |
|     {{{10,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
 | |
|     {{{39,-39}, {40,-40}, {40,-40}, {0,0}}, 4, CellFillMode::kStretch},  // Flat diagonal with 180
 | |
|     {{{40, 40}, {0, 0}, {200, 200}, {0, 0}}, 4, CellFillMode::kStretch},  // Diag w/ an internal 180
 | |
|     {{{0,0}, {1e-2f,0}, {-1e-2f,0}, {0,0}}, 4, CellFillMode::kCenter},  // Circle
 | |
|     {{{400.75f,100.05f}, {400.75f,100.05f}, {100.05f,300.95f}, {100.05f,300.95f}}, 4,
 | |
|      CellFillMode::kStretch},  // Flat line with no turns
 | |
|     {{{0.5f,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
 | |
|     {{{10,0}, {0,0}, {10,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with a 180
 | |
|     {{{1,1}, {2,1}, {1,1}, {1, std::numeric_limits<float>::quiet_NaN()}}, 3,
 | |
|      CellFillMode::kStretch},  // Flat QUAD with a cusp
 | |
|     {{{1,1}, {100,1}, {25,1}, {.3f, std::numeric_limits<float>::quiet_NaN()}}, 3,
 | |
|      CellFillMode::kStretch},  // Flat CONIC with a cusp
 | |
|     {{{1,1}, {100,1}, {25,1}, {1.5f, std::numeric_limits<float>::quiet_NaN()}}, 3,
 | |
|      CellFillMode::kStretch},  // Flat CONIC with a cusp
 | |
| };
 | |
| 
 | |
| static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
 | |
|     if (0 == depth) {
 | |
|         SkRect bounds;
 | |
|         bounds.fLeft = std::min(std::min(P[0].x(), P[1].x()), std::min(P[2].x(), P[3].x()));
 | |
|         bounds.fTop = std::min(std::min(P[0].y(), P[1].y()), std::min(P[2].y(), P[3].y()));
 | |
|         bounds.fRight = std::max(std::max(P[0].x(), P[1].x()), std::max(P[2].x(), P[3].x()));
 | |
|         bounds.fBottom = std::max(std::max(P[0].y(), P[1].y()), std::max(P[2].y(), P[3].y()));
 | |
|         return bounds;
 | |
|     }
 | |
| 
 | |
|     SkPoint chopped[7];
 | |
|     SkChopCubicAt(P, chopped, .5f);
 | |
|     SkRect bounds = calc_tight_cubic_bounds(chopped, depth - 1);
 | |
|     bounds.join(calc_tight_cubic_bounds(chopped+3, depth - 1));
 | |
|     return bounds;
 | |
| }
 | |
| 
 | |
| static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
 | |
|     SkASSERT(1 != T);  // The below does not guarantee lerp(a, b, 1) === b.
 | |
|     return (b - a) * T + a;
 | |
| }
 | |
| 
 | |
| enum class FillMode {
 | |
|     kCenter,
 | |
|     kScale
 | |
| };
 | |
| 
 | |
| static void draw_test(SkCanvas* canvas, SkPaint::Cap cap, SkPaint::Join join) {
 | |
|     SkRandom rand;
 | |
| 
 | |
|     if (canvas->recordingContext() &&
 | |
|         canvas->recordingContext()->priv().caps()->shaderCaps()->tessellationSupport() &&
 | |
|         canvas->recordingContext()->priv().caps()->shaderCaps()->maxTessellationSegments() == 5) {
 | |
|         // The caller successfully overrode the max tessellation segments to 5. Indicate this in the
 | |
|         // background color.
 | |
|         canvas->clear(SkColorSetARGB(255, 64, 0, 0));
 | |
|     } else {
 | |
|         canvas->clear(SK_ColorBLACK);
 | |
|     }
 | |
| 
 | |
|     SkPaint strokePaint;
 | |
|     strokePaint.setAntiAlias(true);
 | |
|     strokePaint.setStrokeWidth(kStrokeWidth);
 | |
|     strokePaint.setStyle(SkPaint::kStroke_Style);
 | |
|     strokePaint.setStrokeCap(cap);
 | |
|     strokePaint.setStrokeJoin(join);
 | |
| 
 | |
|     for (size_t i = 0; i < SK_ARRAY_COUNT(kTrickyCubics); ++i) {
 | |
|         auto [originalPts, numPts, fillMode, scale] = kTrickyCubics[i];
 | |
| 
 | |
|         SkASSERT(numPts <= 4);
 | |
|         SkPoint p[4];
 | |
|         memcpy(p, originalPts, sizeof(SkPoint) * numPts);
 | |
|         for (int j = 0; j < numPts; ++j) {
 | |
|             p[j] *= scale;
 | |
|         }
 | |
|         float w = originalPts[3].fX;
 | |
| 
 | |
|         auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
 | |
|                                          kCellSize, kCellSize);
 | |
| 
 | |
|         SkRect strokeBounds;
 | |
|         if (numPts == 4) {
 | |
|             strokeBounds = calc_tight_cubic_bounds(p);
 | |
|         } else {
 | |
|             SkASSERT(numPts == 3);
 | |
|             SkPoint asCubic[4] = {p[0], lerp(p[0], p[1], 2/3.f), lerp(p[1], p[2], 1/3.f), p[2]};
 | |
|             strokeBounds = calc_tight_cubic_bounds(asCubic);
 | |
|         }
 | |
|         strokeBounds.outset(kStrokeWidth, kStrokeWidth);
 | |
| 
 | |
|         SkMatrix matrix;
 | |
|         if (fillMode == CellFillMode::kStretch) {
 | |
|             matrix = SkMatrix::RectToRect(strokeBounds, cellRect, SkMatrix::kCenter_ScaleToFit);
 | |
|         } else {
 | |
|             matrix.setTranslate(cellRect.x() + kStrokeWidth +
 | |
|                                 (cellRect.width() - strokeBounds.width()) / 2,
 | |
|                                 cellRect.y() + kStrokeWidth +
 | |
|                                 (cellRect.height() - strokeBounds.height()) / 2);
 | |
|         }
 | |
| 
 | |
|         SkAutoCanvasRestore acr(canvas, true);
 | |
|         canvas->concat(matrix);
 | |
|         strokePaint.setStrokeWidth(kStrokeWidth / matrix.getMaxScale());
 | |
|         strokePaint.setColor(rand.nextU() | 0xff808080);
 | |
|         SkPath path = SkPath().moveTo(p[0]);
 | |
|         if (numPts == 4) {
 | |
|             path.cubicTo(p[1], p[2], p[3]);
 | |
|         } else if (w == 1) {
 | |
|             SkASSERT(numPts == 3);
 | |
|             path.quadTo(p[1], p[2]);
 | |
|         } else {
 | |
|             SkASSERT(numPts == 3);
 | |
|             path.conicTo(p[1], p[2], w);
 | |
|         }
 | |
|         canvas->drawPath(path, strokePaint);
 | |
|     }
 | |
| }
 | |
| 
 | |
| DEF_SIMPLE_GM(trickycubicstrokes, canvas, kTestWidth, kTestHeight) {
 | |
|     draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
 | |
| }
 | |
| 
 | |
| DEF_SIMPLE_GM(trickycubicstrokes_roundcaps, canvas, kTestWidth, kTestHeight) {
 | |
|     draw_test(canvas, SkPaint::kRound_Cap, SkPaint::kRound_Join);
 | |
| }
 | |
| 
 | |
| #if SK_GPU_V1
 | |
| #include "src/gpu/ops/TessellationPathRenderer.h"
 | |
| 
 | |
| class TrickyCubicStrokes_tess_segs_5 : public skiagm::GM {
 | |
|     SkString onShortName() override {
 | |
|         return SkString("trickycubicstrokes_tess_segs_5");
 | |
|     }
 | |
| 
 | |
|     SkISize onISize() override {
 | |
|         return SkISize::Make(kTestWidth, kTestHeight);
 | |
|     }
 | |
| 
 | |
|     // Pick a very small, odd (and better yet, prime) number of segments.
 | |
|     //
 | |
|     // - Odd because it makes the tessellation strip asymmetric, which will be important to test for
 | |
|     //   future plans that involve drawing in reverse order.
 | |
|     //
 | |
|     // - >=4 because the tessellator code will just assume we have enough to combine a miter join
 | |
|     //   and line in a single patch. (Requires 4 segments. Spec required minimum is 64.)
 | |
|     static constexpr int kMaxTessellationSegmentsOverride = 5;
 | |
| 
 | |
|     void modifyGrContextOptions(GrContextOptions* options) override {
 | |
|         options->fMaxTessellationSegmentsOverride = kMaxTessellationSegmentsOverride;
 | |
|         options->fAlwaysPreferHardwareTessellation = true;
 | |
|         // Only allow the tessellation path renderer.
 | |
|         options->fGpuPathRenderers = (GpuPathRenderers)((int)options->fGpuPathRenderers &
 | |
|                                                         (int)GpuPathRenderers::kTessellation);
 | |
|     }
 | |
| 
 | |
|     DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
 | |
|         auto dContext = GrAsDirectContext(canvas->recordingContext());
 | |
|         if (!dContext) {
 | |
|             *errorMsg = "GM relies on having access to a live direct context.";
 | |
|             return DrawResult::kSkip;
 | |
|         }
 | |
| 
 | |
|         if (!dContext->priv().caps()->shaderCaps()->tessellationSupport() ||
 | |
|             !skgpu::v1::TessellationPathRenderer::IsSupported(*dContext->priv().caps())) {
 | |
|             errorMsg->set("Tessellation not supported.");
 | |
|             return DrawResult::kSkip;
 | |
|         }
 | |
|         auto opts = dContext->priv().drawingManager()->testingOnly_getOptionsForPathRendererChain();
 | |
|         if (!(opts.fGpuPathRenderers & GpuPathRenderers::kTessellation)) {
 | |
|             errorMsg->set("TessellationPathRenderer disabled.");
 | |
|             return DrawResult::kSkip;
 | |
|         }
 | |
|         if (dContext->priv().caps()->shaderCaps()->maxTessellationSegments() !=
 | |
|             kMaxTessellationSegmentsOverride) {
 | |
|             errorMsg->set("modifyGrContextOptions did not affect maxTessellationSegments. "
 | |
|                           "(Are you running viewer? If so use '--maxTessellationSegments 5'.)");
 | |
|             return DrawResult::kFail;
 | |
|         }
 | |
|         // Suppress a tessellator warning message that caps.maxTessellationSegments is too small.
 | |
|         GrRecordingContextPriv::AutoSuppressWarningMessages aswm(dContext);
 | |
|         draw_test(canvas, SkPaint::kButt_Cap, SkPaint::kMiter_Join);
 | |
|         return DrawResult::kOk;
 | |
|     }
 | |
| };
 | |
| 
 | |
| DEF_GM( return new TrickyCubicStrokes_tess_segs_5; )
 | |
| #endif // SK_GPU_V1
 |