| /* | |
| * Copyright 2019 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/gpu/ganesh/geometry/GrQuad.h" | |
| #include "src/gpu/ganesh/geometry/GrQuadUtils.h" | |
| #include "tests/Test.h" | |
| #define ASSERT(cond) REPORTER_ASSERT(r, cond) | |
| #define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__) | |
| #define TEST(name) DEF_TEST(GrQuadCrop##name, r) | |
| #define ASSERT_NEARLY_EQUAL(expected, actual) \ | |
| ASSERTF(SkScalarNearlyEqual(expected, actual), "expected: %f, actual: %f", \ | |
| expected, actual) | |
| // Make the base rect contain the origin and have unique edge values so that each transform | |
| // produces a different axis-aligned rectangle. | |
| static const SkRect kDrawRect = SkRect::MakeLTRB(-5.f, -6.f, 10.f, 11.f); | |
| static void run_crop_axis_aligned_test(skiatest::Reporter* r, const SkRect& clipRect, GrAA clipAA, | |
| const SkMatrix& viewMatrix, const SkMatrix* localMatrix) { | |
| // Should use run_crop_fully_covers_test for non-rect matrices | |
| SkASSERT(viewMatrix.rectStaysRect()); | |
| DrawQuad quad = {GrQuad::MakeFromRect(kDrawRect, viewMatrix), | |
| GrQuad::MakeFromRect(kDrawRect, localMatrix ? *localMatrix : SkMatrix::I()), | |
| clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll}; | |
| bool exact = GrQuadUtils::CropToRect(clipRect, clipAA, &quad, /* calc. locals */ !!localMatrix); | |
| ASSERTF(exact, "Expected exact crop"); | |
| ASSERTF(quad.fDevice.quadType() == GrQuad::Type::kAxisAligned, | |
| "Expected quad to remain axis-aligned"); | |
| // Since we remained a rectangle, the bounds will exactly match the coordinates | |
| SkRect expectedBounds = viewMatrix.mapRect(kDrawRect); | |
| SkAssertResult(expectedBounds.intersect(clipRect)); | |
| SkRect actualBounds = quad.fDevice.bounds(); | |
| ASSERT_NEARLY_EQUAL(expectedBounds.fLeft, actualBounds.fLeft); | |
| ASSERT_NEARLY_EQUAL(expectedBounds.fTop, actualBounds.fTop); | |
| ASSERT_NEARLY_EQUAL(expectedBounds.fRight, actualBounds.fRight); | |
| ASSERT_NEARLY_EQUAL(expectedBounds.fBottom, actualBounds.fBottom); | |
| // Confirm that local coordinates match up with clipped edges and the transform | |
| SkMatrix invViewMatrix; | |
| SkAssertResult(viewMatrix.invert(&invViewMatrix)); | |
| if (localMatrix) { | |
| SkMatrix toLocal = SkMatrix::Concat(*localMatrix, invViewMatrix); | |
| for (int p = 0; p < 4; ++p) { | |
| SkPoint expectedPoint = quad.fDevice.point(p); | |
| toLocal.mapPoints(&expectedPoint, 1); | |
| SkPoint actualPoint = quad.fLocal.point(p); | |
| ASSERT_NEARLY_EQUAL(expectedPoint.fX, actualPoint.fX); | |
| ASSERT_NEARLY_EQUAL(expectedPoint.fY, actualPoint.fY); | |
| } | |
| } | |
| // Confirm that the edge flags match, by mapping clip rect to drawRect space and | |
| // comparing to the original draw rect edges | |
| SkRect drawClip = invViewMatrix.mapRect(clipRect); | |
| if (drawClip.fLeft > kDrawRect.fLeft) { | |
| if (clipAA == GrAA::kYes) { | |
| ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kLeft, "Expected left edge AA set"); | |
| } else { | |
| ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kLeft), "Expected left edge AA unset"); | |
| } | |
| } | |
| if (drawClip.fRight < kDrawRect.fRight) { | |
| if (clipAA == GrAA::kYes) { | |
| ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kRight, "Expected right edge AA set"); | |
| } else { | |
| ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kRight), "Expected right edge AA unset"); | |
| } | |
| } | |
| if (drawClip.fTop > kDrawRect.fTop) { | |
| if (clipAA == GrAA::kYes) { | |
| ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kTop, "Expected top edge AA set"); | |
| } else { | |
| ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kTop), "Expected top edge AA unset"); | |
| } | |
| } | |
| if (drawClip.fBottom < kDrawRect.fBottom) { | |
| if (clipAA == GrAA::kYes) { | |
| ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kBottom, "Expected bottom edge AA set"); | |
| } else { | |
| ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kBottom), "Expected bottom edge AA unset"); | |
| } | |
| } | |
| } | |
| static void run_crop_fully_covered_test(skiatest::Reporter* r, GrAA clipAA, | |
| const SkMatrix& viewMatrix, const SkMatrix* localMatrix) { | |
| // Should use run_crop_axis_aligned for rect transforms since that verifies more behavior | |
| SkASSERT(!viewMatrix.rectStaysRect()); | |
| // Test what happens when the geometry fully covers the crop rect. Given a fixed crop, | |
| // use the provided view matrix to derive the "input" geometry that we know covers the crop. | |
| SkMatrix invViewMatrix; | |
| SkAssertResult(viewMatrix.invert(&invViewMatrix)); | |
| SkRect containsCrop = kDrawRect; // Use kDrawRect as the crop rect for this test | |
| containsCrop.outset(10.f, 10.f); | |
| SkRect drawRect = invViewMatrix.mapRect(containsCrop); | |
| DrawQuad quad = {GrQuad::MakeFromRect(drawRect, viewMatrix), | |
| GrQuad::MakeFromRect(drawRect, localMatrix ? *localMatrix : SkMatrix::I()), | |
| clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll}; | |
| if (localMatrix) { | |
| DrawQuad originalQuad = quad; | |
| bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &quad); | |
| // Currently non-rect matrices don't know how to update local coordinates, so the crop | |
| // doesn't know how to restrict itself and should leave the inputs unmodified | |
| ASSERTF(!exact, "Expected crop to be not exact"); | |
| ASSERTF(quad.fEdgeFlags == originalQuad.fEdgeFlags, | |
| "Expected edge flags not to be modified"); | |
| for (int i = 0; i < 4; ++i) { | |
| ASSERT_NEARLY_EQUAL(originalQuad.fDevice.x(i), quad.fDevice.x(i)); | |
| ASSERT_NEARLY_EQUAL(originalQuad.fDevice.y(i), quad.fDevice.y(i)); | |
| ASSERT_NEARLY_EQUAL(originalQuad.fDevice.w(i), quad.fDevice.w(i)); | |
| ASSERT_NEARLY_EQUAL(originalQuad.fLocal.x(i), quad.fLocal.x(i)); | |
| ASSERT_NEARLY_EQUAL(originalQuad.fLocal.y(i), quad.fLocal.y(i)); | |
| ASSERT_NEARLY_EQUAL(originalQuad.fLocal.w(i), quad.fLocal.w(i)); | |
| } | |
| } else { | |
| // Since no local coordinates were provided, and the input draw geometry is known to | |
| // fully cover the crop rect, the quad should be updated to match cropRect exactly, | |
| // unless it's perspective in which case we don't do anything since the code isn't | |
| // numerically robust enough. | |
| DrawQuad originalQuad = quad; | |
| bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &quad, /* calc. local */ false); | |
| if (originalQuad.fDevice.quadType() == GrQuad::Type::kPerspective) { | |
| ASSERTF(!exact, "Expected no change for perspective"); | |
| for (int i = 0; i < 4; ++i) { | |
| ASSERTF(originalQuad.fDevice.x(i) == quad.fDevice.x(i)); | |
| ASSERTF(originalQuad.fDevice.y(i) == quad.fDevice.y(i)); | |
| ASSERTF(originalQuad.fDevice.w(i) == quad.fDevice.w(i)); | |
| } | |
| return; | |
| } | |
| ASSERTF(exact, "Expected crop to be exact"); | |
| GrQuadAAFlags expectedFlags = clipAA == GrAA::kYes ? GrQuadAAFlags::kAll | |
| : GrQuadAAFlags::kNone; | |
| ASSERTF(expectedFlags == quad.fEdgeFlags, | |
| "Expected edge flags do not match clip AA setting"); | |
| ASSERTF(quad.fDevice.quadType() == GrQuad::Type::kAxisAligned, "Unexpected quad type"); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, quad.fDevice.x(0)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fTop, quad.fDevice.y(0)); | |
| ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(0)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, quad.fDevice.x(1)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, quad.fDevice.y(1)); | |
| ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(1)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fRight, quad.fDevice.x(2)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fTop, quad.fDevice.y(2)); | |
| ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(2)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fRight, quad.fDevice.x(3)); | |
| ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, quad.fDevice.y(3)); | |
| ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(3)); | |
| } | |
| } | |
| static void test_axis_aligned_all_clips(skiatest::Reporter* r, const SkMatrix& viewMatrix, | |
| const SkMatrix* localMatrix) { | |
| static const float kInsideEdge = SkScalarAbs(kDrawRect.fLeft) - 1.f; | |
| static const float kOutsideEdge = SkScalarAbs(kDrawRect.fBottom) + 1.f; | |
| static const float kIntersectEdge = SkScalarAbs(kDrawRect.fTop) + 1.f; | |
| static const SkRect kInsideClipRect = SkRect::MakeLTRB(-kInsideEdge, -kInsideEdge, | |
| kInsideEdge, kInsideEdge); | |
| static const SkRect kContainsClipRect = SkRect::MakeLTRB(-kOutsideEdge, -kOutsideEdge, | |
| kOutsideEdge, kOutsideEdge); | |
| static const SkRect kXYAxesClipRect = SkRect::MakeLTRB(-kIntersectEdge, -kIntersectEdge, | |
| kIntersectEdge, kIntersectEdge); | |
| static const SkRect kXAxisClipRect = SkRect::MakeLTRB(-kIntersectEdge, -kOutsideEdge, | |
| kIntersectEdge, kOutsideEdge); | |
| static const SkRect kYAxisClipRect = SkRect::MakeLTRB(-kOutsideEdge, -kIntersectEdge, | |
| kOutsideEdge, kIntersectEdge); | |
| run_crop_axis_aligned_test(r, kInsideClipRect, GrAA::kNo, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kContainsClipRect, GrAA::kNo, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kXYAxesClipRect, GrAA::kNo, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kXAxisClipRect, GrAA::kNo, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kYAxisClipRect, GrAA::kNo, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kInsideClipRect, GrAA::kYes, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kContainsClipRect, GrAA::kYes, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kXYAxesClipRect, GrAA::kYes, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kXAxisClipRect, GrAA::kYes, viewMatrix, localMatrix); | |
| run_crop_axis_aligned_test(r, kYAxisClipRect, GrAA::kYes, viewMatrix, localMatrix); | |
| } | |
| static void test_axis_aligned(skiatest::Reporter* r, const SkMatrix& viewMatrix) { | |
| test_axis_aligned_all_clips(r, viewMatrix, nullptr); | |
| SkMatrix normalized = SkMatrix::RectToRect(kDrawRect, SkRect::MakeWH(1.f, 1.f)); | |
| test_axis_aligned_all_clips(r, viewMatrix, &normalized); | |
| SkMatrix rotated; | |
| rotated.setRotate(45.f); | |
| test_axis_aligned_all_clips(r, viewMatrix, &rotated); | |
| SkMatrix perspective; | |
| perspective.setPerspY(0.001f); | |
| perspective.setSkewX(8.f / 25.f); | |
| test_axis_aligned_all_clips(r, viewMatrix, &perspective); | |
| } | |
| static void test_crop_fully_covered(skiatest::Reporter* r, const SkMatrix& viewMatrix) { | |
| run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, nullptr); | |
| run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, nullptr); | |
| SkMatrix normalized = SkMatrix::RectToRect(kDrawRect, SkRect::MakeWH(1.f, 1.f)); | |
| run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, &normalized); | |
| run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, &normalized); | |
| SkMatrix rotated; | |
| rotated.setRotate(45.f); | |
| run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, &rotated); | |
| run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, &rotated); | |
| SkMatrix perspective; | |
| perspective.setPerspY(0.001f); | |
| perspective.setSkewX(8.f / 25.f); | |
| run_crop_fully_covered_test(r, GrAA::kNo, viewMatrix, &perspective); | |
| run_crop_fully_covered_test(r, GrAA::kYes, viewMatrix, &perspective); | |
| } | |
| TEST(AxisAligned) { | |
| test_axis_aligned(r, SkMatrix::I()); | |
| test_axis_aligned(r, SkMatrix::Scale(-1.f, 1.f)); | |
| test_axis_aligned(r, SkMatrix::Scale(1.f, -1.f)); | |
| SkMatrix rotation; | |
| rotation.setRotate(90.f); | |
| test_axis_aligned(r, rotation); | |
| rotation.setRotate(180.f); | |
| test_axis_aligned(r, rotation); | |
| rotation.setRotate(270.f); | |
| test_axis_aligned(r, rotation); | |
| } | |
| TEST(FullyCovered) { | |
| SkMatrix rotation; | |
| rotation.setRotate(34.f); | |
| test_crop_fully_covered(r, rotation); | |
| SkMatrix skew; | |
| skew.setSkewX(0.3f); | |
| skew.setSkewY(0.04f); | |
| test_crop_fully_covered(r, skew); | |
| SkMatrix perspective; | |
| perspective.setPerspX(0.001f); | |
| perspective.setSkewY(8.f / 25.f); | |
| test_crop_fully_covered(r, perspective); | |
| } |