[graphite] Add an IntersectionTree class
Implements a BSP tree with NEON/SSE optimizations that tracks non-overlapping regions. This object can be used batch sets of paths into non-overlapping draws. Performance on AppleM1 with our existing benchmarks looks very promising: desk_motionmarkarcs.skp 1227 paths -> 69 draws 450us desk_motionmarksuits.skp 1417 paths -> 26 draws 201us desk_chalkboard.skp 1940 paths -> 11 draws 84us desk_ynevsvg.skp 859 paths -> 10 draws 31us desk_micrographygirl.skp 318 paths -> 29 draws 11us Bug: skia:12466 Change-Id: I847a93ed726dea10cb403cb76e578bd81eb920d2 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/460298 Commit-Queue: Chris Dalton <csmartdalton@google.com> Reviewed-by: Herb Derby <herb@google.com>
This commit is contained in:
parent
b3460f9979
commit
2fceb21cb7
3
BUILD.gn
3
BUILD.gn
@ -2038,6 +2038,9 @@ if (skia_enable_tools) {
|
||||
import("gn/bench.gni")
|
||||
test_lib("bench") {
|
||||
sources = bench_sources
|
||||
if (skia_enable_graphite) {
|
||||
sources += graphite_bench_sources
|
||||
}
|
||||
if (!skia_enable_skgpu_v1) {
|
||||
sources -= skgpu_v1_bench_sources
|
||||
}
|
||||
|
167
bench/graphite/IntersectionTreeBench.cpp
Normal file
167
bench/graphite/IntersectionTreeBench.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2021 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
#include "bench/Benchmark.h"
|
||||
#include "experimental/graphite/src/geom/IntersectionTree.h"
|
||||
#include "include/core/SkPaint.h"
|
||||
#include "include/core/SkPath.h"
|
||||
#include "include/utils/SkRandom.h"
|
||||
#include "src/core/SkMathPriv.h"
|
||||
#include "tools/ToolUtils.h"
|
||||
#include "tools/flags/CommandLineFlags.h"
|
||||
|
||||
static DEFINE_string(intersectionTreeFile, "",
|
||||
"svg or skp for the IntersectionTree bench to sniff paths from.");
|
||||
|
||||
namespace skgpu {
|
||||
|
||||
class IntersectionTreeBench : public Benchmark {
|
||||
protected:
|
||||
const char* onGetName() final { return fName.c_str(); }
|
||||
|
||||
bool isSuitableFor(Backend backend) override {
|
||||
return backend == kNonRendering_Backend;
|
||||
}
|
||||
|
||||
void onDelayedSetup() final {
|
||||
SkTArray<SkRect> rects;
|
||||
this->gatherRects(&rects);
|
||||
fRectCount = rects.count();
|
||||
fRects = fAlignedAllocator.makeArray<Rect>(fRectCount);
|
||||
for (int i = 0; i < fRectCount; ++i) {
|
||||
fRects[i] = rects[i];
|
||||
}
|
||||
fRectBufferA = fAlignedAllocator.makeArray<Rect>(fRectCount);
|
||||
fRectBufferB = fAlignedAllocator.makeArray<Rect>(fRectCount);
|
||||
}
|
||||
|
||||
virtual void gatherRects(SkTArray<SkRect>* rects) = 0;
|
||||
|
||||
void onDraw(int loops, SkCanvas*) final {
|
||||
for (int i = 0; i < loops; ++i) {
|
||||
this->doBench();
|
||||
}
|
||||
}
|
||||
|
||||
void doBench() {
|
||||
Rect* rects = fRects;
|
||||
Rect* collided = fRectBufferA;
|
||||
int rectCount = fRectCount;
|
||||
fNumTrees = 0;
|
||||
while (rectCount > 0) {
|
||||
IntersectionTree intersectionTree;
|
||||
int collidedCount = 0;
|
||||
for (int i = 0; i < rectCount; ++i) {
|
||||
if (!intersectionTree.add(rects[i])) {
|
||||
collided[collidedCount++] = rects[i];
|
||||
}
|
||||
}
|
||||
std::swap(rects, collided);
|
||||
if (collided == fRects) {
|
||||
collided = fRectBufferB;
|
||||
}
|
||||
rectCount = collidedCount;
|
||||
++fNumTrees;
|
||||
}
|
||||
}
|
||||
|
||||
SkString fName;
|
||||
SkArenaAlloc fAlignedAllocator{0};
|
||||
int fRectCount;
|
||||
Rect* fRects;
|
||||
Rect* fRectBufferA;
|
||||
Rect* fRectBufferB;
|
||||
int fNumTrees = 0;
|
||||
};
|
||||
|
||||
class RandomIntersectionBench : public IntersectionTreeBench {
|
||||
public:
|
||||
RandomIntersectionBench(int numRandomRects) : fNumRandomRects(numRandomRects) {
|
||||
fName.printf("IntersectionTree_%i", numRandomRects);
|
||||
}
|
||||
|
||||
private:
|
||||
void gatherRects(SkTArray<SkRect>* rects) override {
|
||||
SkRandom rand;
|
||||
for (int i = 0; i < fNumRandomRects; ++i) {
|
||||
rects->push_back(SkRect::MakeXYWH(rand.nextRangeF(0, 2000),
|
||||
rand.nextRangeF(0, 2000),
|
||||
rand.nextRangeF(0, 70),
|
||||
rand.nextRangeF(0, 70)));
|
||||
}
|
||||
}
|
||||
|
||||
const int fNumRandomRects;
|
||||
};
|
||||
|
||||
class FileIntersectionBench : public IntersectionTreeBench {
|
||||
public:
|
||||
FileIntersectionBench() {
|
||||
if (FLAGS_intersectionTreeFile.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const char* filename = strrchr(FLAGS_intersectionTreeFile[0], '/');
|
||||
if (filename) {
|
||||
++filename;
|
||||
} else {
|
||||
filename = FLAGS_intersectionTreeFile[0];
|
||||
}
|
||||
fName.printf("IntersectionTree_file_%s", filename);
|
||||
}
|
||||
|
||||
private:
|
||||
bool isSuitableFor(Backend backend) final {
|
||||
if (FLAGS_intersectionTreeFile.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return IntersectionTreeBench::isSuitableFor(backend);
|
||||
}
|
||||
|
||||
void gatherRects(SkTArray<SkRect>* rects) override {
|
||||
if (FLAGS_intersectionTreeFile.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ToolUtils::sniff_paths(FLAGS_intersectionTreeFile[0], [&](const SkMatrix& matrix,
|
||||
const SkPath& path,
|
||||
const SkPaint& paint) {
|
||||
if (paint.getStyle() == SkPaint::kStroke_Style) {
|
||||
return; // Goes to stroker.
|
||||
}
|
||||
if (path.isConvex()) {
|
||||
return; // Goes to convex renderer.
|
||||
}
|
||||
int numVerbs = path.countVerbs();
|
||||
SkRect drawBounds = matrix.mapRect(path.getBounds());
|
||||
float gpuFragmentWork = drawBounds.height() * drawBounds.width();
|
||||
float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs); // N log N.
|
||||
constexpr static float kCpuWeight = 512;
|
||||
constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
|
||||
if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
|
||||
return; // Goes to inner triangulator.
|
||||
}
|
||||
rects->push_back(drawBounds);
|
||||
});
|
||||
SkDebugf(">> Found %i stencil/cover paths in %s <<\n",
|
||||
rects->count(), FLAGS_intersectionTreeFile[0]);
|
||||
}
|
||||
|
||||
void onPerCanvasPostDraw(SkCanvas*) override {
|
||||
if (FLAGS_intersectionTreeFile.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SkDebugf(">> Reordered %s into %i different stencil/cover draws <<\n",
|
||||
FLAGS_intersectionTreeFile[0], fNumTrees);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace skgpu
|
||||
|
||||
DEF_BENCH( return new skgpu::RandomIntersectionBench(100); )
|
||||
DEF_BENCH( return new skgpu::RandomIntersectionBench(500); )
|
||||
DEF_BENCH( return new skgpu::RandomIntersectionBench(1000); )
|
||||
DEF_BENCH( return new skgpu::RandomIntersectionBench(5000); )
|
||||
DEF_BENCH( return new skgpu::RandomIntersectionBench(10000); )
|
||||
DEF_BENCH( return new skgpu::FileIntersectionBench(); ) // Sniffs --intersectionTreeFile
|
209
experimental/graphite/src/geom/IntersectionTree.cpp
Normal file
209
experimental/graphite/src/geom/IntersectionTree.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "experimental/graphite/src/geom/IntersectionTree.h"
|
||||
|
||||
#include "include/private/SkTPin.h"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace skgpu {
|
||||
|
||||
// BSP node. Space is partitioned by an either vertical or horizontal line. Note that if a rect
|
||||
// straddles the partition line, it will need to go on both sides of the tree.
|
||||
template<IntersectionTree::SplitType kSplitType>
|
||||
class IntersectionTree::TreeNode final : public Node {
|
||||
public:
|
||||
TreeNode(float splitCoord, Node* lo, Node* hi)
|
||||
: fSplitCoord(splitCoord), fLo(lo), fHi(hi) {
|
||||
}
|
||||
|
||||
bool intersects(Rect rect) override {
|
||||
if (GetLoVal(rect) < fSplitCoord && fLo->intersects(rect)) {
|
||||
return true;
|
||||
}
|
||||
if (GetHiVal(rect) > fSplitCoord && fHi->intersects(rect)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Node* addNonIntersecting(Rect rect, SkArenaAlloc* arena) override {
|
||||
if (GetLoVal(rect) < fSplitCoord) {
|
||||
fLo = fLo->addNonIntersecting(rect, arena);
|
||||
}
|
||||
if (GetHiVal(rect) > fSplitCoord) {
|
||||
fHi = fHi->addNonIntersecting(rect, arena);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
SK_ALWAYS_INLINE static float GetLoVal(const Rect& rect) {
|
||||
return (kSplitType == SplitType::kX) ? rect.left() : rect.top();
|
||||
}
|
||||
SK_ALWAYS_INLINE static float GetHiVal(const Rect& rect) {
|
||||
return (kSplitType == SplitType::kX) ? rect.right() : rect.bot();
|
||||
}
|
||||
|
||||
float fSplitCoord;
|
||||
Node* fLo;
|
||||
Node* fHi;
|
||||
};
|
||||
|
||||
// Leaf node. Rects are kept in a simple list and intersection testing is performed by brute force.
|
||||
class IntersectionTree::LeafNode final : public Node {
|
||||
public:
|
||||
// Max number of rects to store in this node before splitting. With SSE/NEON optimizations, ~64
|
||||
// brute force rect comparisons seems to be the optimal number.
|
||||
constexpr static int kMaxRectsInList = 64;
|
||||
|
||||
LeafNode() {
|
||||
this->popAll();
|
||||
// Initialize our arrays with maximally negative rects. These have the advantage of always
|
||||
// failing intersection tests, thus allowing us to test for intersection beyond fNumRects
|
||||
// without failing.
|
||||
constexpr static float infinity = std::numeric_limits<float>::infinity();
|
||||
std::fill_n(fLefts, kMaxRectsInList, infinity);
|
||||
std::fill_n(fTops, kMaxRectsInList, infinity);
|
||||
std::fill_n(fNegRights, kMaxRectsInList, infinity);
|
||||
std::fill_n(fNegBots, kMaxRectsInList, infinity);
|
||||
}
|
||||
|
||||
void popAll() {
|
||||
fNumRects = 0;
|
||||
fSplittableBounds = -std::numeric_limits<float>::infinity();
|
||||
fRectValsSum = 0;
|
||||
// Leave the rect arrays untouched. Since we know they are either already valid in the tree,
|
||||
// or else maximally negative, this allows the future list to check for intersection beyond
|
||||
// fNumRects without failing.
|
||||
}
|
||||
|
||||
bool intersects(Rect rect) override {
|
||||
// Test for intersection in sets of 4. Since all the data in our rect arrays is either
|
||||
// maximally negative, or valid from somewhere else in the tree, we can test beyond
|
||||
// fNumRects without failing.
|
||||
static_assert(kMaxRectsInList % 4 == 0);
|
||||
SkASSERT(fNumRects <= kMaxRectsInList);
|
||||
float4 comp = Rect::ComplementRect(rect).fVals;
|
||||
for (int i = 0; i < fNumRects; i += 4) {
|
||||
float4 l = float4::Load(fLefts + i);
|
||||
float4 t = float4::Load(fTops + i);
|
||||
float4 nr = float4::Load(fNegRights + i);
|
||||
float4 nb = float4::Load(fNegBots + i);
|
||||
if (any((l < comp[0]) &
|
||||
(t < comp[1]) &
|
||||
(nr < comp[2]) &
|
||||
(nb < comp[3]))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Node* addNonIntersecting(Rect rect, SkArenaAlloc* arena) override {
|
||||
if (fNumRects == kMaxRectsInList) {
|
||||
// The new rect doesn't fit. Split our rect list first and then add.
|
||||
return this->split(arena)->addNonIntersecting(rect, arena);
|
||||
}
|
||||
this->appendToList(rect);
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
void appendToList(Rect rect) {
|
||||
SkASSERT(fNumRects < kMaxRectsInList);
|
||||
int i = fNumRects++;
|
||||
// [maxLeft, maxTop, -minRight, -minBot]
|
||||
fSplittableBounds = max(fSplittableBounds, rect.vals());
|
||||
fRectValsSum += rect.vals(); // [sum(left), sum(top), -sum(right), -sum(bot)]
|
||||
fLefts[i] = rect.vals()[0];
|
||||
fTops[i] = rect.vals()[1];
|
||||
fNegRights[i] = rect.vals()[2];
|
||||
fNegBots[i] = rect.vals()[3];
|
||||
}
|
||||
|
||||
Rect loadRect(int i) const {
|
||||
return Rect::FromVals(float4(fLefts[i], fTops[i], fNegRights[i], fNegBots[i]));
|
||||
}
|
||||
|
||||
// Splits this node with a new LeafNode, then returns a TreeNode that reuses our "this" pointer
|
||||
// along with the new node.
|
||||
IntersectionTree::Node* split(SkArenaAlloc* arena) {
|
||||
// This should only get called when our list is full.
|
||||
SkASSERT(fNumRects == kMaxRectsInList);
|
||||
|
||||
// Since rects cannot overlap, there will always be a split that places at least one pairing
|
||||
// of rects on opposite sides. The region:
|
||||
//
|
||||
// fSplittableBounds == [maxLeft, maxTop, -minRight, -minBot] == [r, b, -l, -t]
|
||||
//
|
||||
// Represents the region of splits that guarantee a strict subdivision of our rect list.
|
||||
float2 splittableSize = fSplittableBounds.xy() + fSplittableBounds.zw(); // == [r-l, b-t]
|
||||
SkASSERT(max(splittableSize) >= 0);
|
||||
SplitType splitType = (splittableSize.x() > splittableSize.y()) ? SplitType::kX
|
||||
: SplitType::kY;
|
||||
|
||||
float splitCoord;
|
||||
const float *loVals, *negHiVals;
|
||||
if (splitType == SplitType::kX) {
|
||||
// Split horizontally, at the geometric midpoint if it falls within the splittable
|
||||
// bounds.
|
||||
splitCoord = (fRectValsSum.x() - fRectValsSum.z()) * (.5f/kMaxRectsInList);
|
||||
splitCoord = SkTPin(splitCoord, -fSplittableBounds.z(), fSplittableBounds.x());
|
||||
loVals = fLefts;
|
||||
negHiVals = fNegRights;
|
||||
} else {
|
||||
// Split vertically, at the geometric midpoint if it falls within the splittable bounds.
|
||||
splitCoord = (fRectValsSum.y() - fRectValsSum.w()) * (.5f/kMaxRectsInList);
|
||||
splitCoord = SkTPin(splitCoord, -fSplittableBounds.w(), fSplittableBounds.y());
|
||||
loVals = fTops;
|
||||
negHiVals = fNegBots;
|
||||
}
|
||||
|
||||
// Split "this", leaving all rects below "splitCoord" in this, and placing all rects above
|
||||
// splitCoord in "hiNode". There may be some reduncancy between lists, but we made sure to
|
||||
// select a split that would leave both lists strictly smaller than the original.
|
||||
LeafNode* hiNode = arena->make<LeafNode>();
|
||||
int numCombinedRects = fNumRects;
|
||||
float negSplitCoord = -splitCoord;
|
||||
this->popAll();
|
||||
for (int i = 0; i < numCombinedRects; ++i) {
|
||||
Rect rect = this->loadRect(i);
|
||||
if (loVals[i] < splitCoord) {
|
||||
this->appendToList(rect);
|
||||
}
|
||||
if (negHiVals[i] < negSplitCoord) {
|
||||
hiNode->appendToList(rect);
|
||||
}
|
||||
}
|
||||
|
||||
SkASSERT(0 < fNumRects && fNumRects < numCombinedRects);
|
||||
SkASSERT(0 < hiNode->fNumRects && hiNode->fNumRects < numCombinedRects);
|
||||
|
||||
return (splitType == SplitType::kX)
|
||||
? (Node*)arena->make<TreeNode<SplitType::kX>>(splitCoord, this, hiNode)
|
||||
: (Node*)arena->make<TreeNode<SplitType::kY>>(splitCoord, this, hiNode);
|
||||
}
|
||||
|
||||
int fNumRects;
|
||||
float4 fSplittableBounds; // [maxLeft, maxTop, -minRight, -minBot]
|
||||
float4 fRectValsSum; // [sum(left), sum(top), -sum(right), -sum(bot)]
|
||||
float fLefts[kMaxRectsInList];
|
||||
float fTops[kMaxRectsInList];
|
||||
float fNegRights[kMaxRectsInList];
|
||||
float fNegBots[kMaxRectsInList];
|
||||
};
|
||||
|
||||
IntersectionTree::IntersectionTree()
|
||||
: fRoot(fArena.make<LeafNode>()) {
|
||||
static_assert(kTreeNodeSize == sizeof(TreeNode<SplitType::kX>));
|
||||
static_assert(kTreeNodeSize == sizeof(TreeNode<SplitType::kY>));
|
||||
static_assert(kLeafNodeSize == sizeof(LeafNode));
|
||||
}
|
||||
|
||||
} // namespace skgpu
|
63
experimental/graphite/src/geom/IntersectionTree.h
Normal file
63
experimental/graphite/src/geom/IntersectionTree.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef skgpu_geom_IntersectionTree_DEFINED
|
||||
#define skgpu_geom_IntersectionTree_DEFINED
|
||||
|
||||
#include "experimental/graphite/src/geom/Rect.h"
|
||||
#include "src/core/SkArenaAlloc.h"
|
||||
|
||||
namespace skgpu {
|
||||
|
||||
// Maintains a collection of non-overlapping rectangles.
|
||||
//
|
||||
// add() either adds the given rect to the collection, or returns false if it intersected with a
|
||||
// rect already in the collection.
|
||||
class IntersectionTree {
|
||||
public:
|
||||
IntersectionTree();
|
||||
|
||||
bool add(Rect rect) {
|
||||
if (rect.isEmptyNegativeOrNaN()) {
|
||||
// Empty and undefined rects can simply pass without modifying the tree.
|
||||
return true;
|
||||
}
|
||||
if (!fRoot->intersects(rect)) {
|
||||
fRoot = fRoot->addNonIntersecting(rect, &fArena);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
class Node {
|
||||
public:
|
||||
virtual ~Node() = default;
|
||||
|
||||
virtual bool intersects(Rect) = 0;
|
||||
virtual Node* addNonIntersecting(Rect, SkArenaAlloc*) = 0;
|
||||
};
|
||||
|
||||
enum class SplitType : bool {
|
||||
kX,
|
||||
kY
|
||||
};
|
||||
|
||||
template<SplitType kSplitType> class TreeNode;
|
||||
class LeafNode;
|
||||
|
||||
constexpr static int kTreeNodeSize = 16 + sizeof(Node*) * 2;
|
||||
constexpr static int kLeafNodeSize = 16 + (2 + 64) * sizeof(float4);
|
||||
constexpr static int kPadSize = 256; // For footers and alignment.
|
||||
SkArenaAlloc fArena{kLeafNodeSize + kTreeNodeSize + kPadSize*2};
|
||||
Node* fRoot;
|
||||
};
|
||||
|
||||
|
||||
} // namespace skgpu
|
||||
|
||||
#endif // skgpu_geom_IntersectionTree_DEFINED
|
@ -129,6 +129,8 @@ bench_sources = [
|
||||
"$_bench/WriterBench.cpp",
|
||||
]
|
||||
|
||||
graphite_bench_sources = [ "$_bench/graphite/IntersectionTreeBench.cpp" ]
|
||||
|
||||
skgpu_v1_bench_sources = [
|
||||
"$_bench/BulkRectBench.cpp",
|
||||
"$_bench/ClearBench.cpp",
|
||||
|
@ -65,6 +65,8 @@ skia_graphite_sources = [
|
||||
"$_src/UniformDataManager.cpp",
|
||||
"$_src/UniformDataManager.h",
|
||||
"$_src/geom/BoundsManager.h",
|
||||
"$_src/geom/IntersectionTree.cpp",
|
||||
"$_src/geom/IntersectionTree.h",
|
||||
"$_src/geom/Rect.h",
|
||||
"$_src/geom/Shape.cpp",
|
||||
"$_src/geom/Shape.h",
|
||||
|
@ -328,6 +328,7 @@ metal_tests_sources = [
|
||||
|
||||
graphite_tests_sources = [
|
||||
"$_tests/graphite/CommandBufferTest.cpp",
|
||||
"$_tests/graphite/IntersectionTreeTest.cpp",
|
||||
"$_tests/graphite/MaskTest.cpp",
|
||||
"$_tests/graphite/RectTest.cpp",
|
||||
"$_tests/graphite/ShapeTest.cpp",
|
||||
|
75
tests/graphite/IntersectionTreeTest.cpp
Normal file
75
tests/graphite/IntersectionTreeTest.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "experimental/graphite/src/geom/IntersectionTree.h"
|
||||
#include "include/utils/SkRandom.h"
|
||||
#include "tests/Test.h"
|
||||
|
||||
namespace skgpu {
|
||||
|
||||
class SimpleIntersectionTree {
|
||||
public:
|
||||
bool add(SkRect rect) {
|
||||
for (const SkRect& r : fRects) {
|
||||
if (r.intersects(rect)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fRects.push_back(rect);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<SkRect> fRects;
|
||||
};
|
||||
|
||||
#define CHECK(A) REPORTER_ASSERT(reporter, A)
|
||||
|
||||
DEF_GRAPHITE_TEST(skgpu_IntersectionTree, reporter) {
|
||||
SkRandom rand;
|
||||
{
|
||||
SimpleIntersectionTree simpleTree;
|
||||
IntersectionTree tree;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
Rect rect = Rect::XYWH(rand.nextRangeF(0, 500),
|
||||
rand.nextRangeF(0, 500),
|
||||
rand.nextRangeF(0, 70),
|
||||
rand.nextRangeF(0, 70));
|
||||
CHECK(tree.add(rect) == simpleTree.add({rect.left(),
|
||||
rect.top(),
|
||||
rect.right(),
|
||||
rect.bot()}));
|
||||
}
|
||||
}
|
||||
{
|
||||
SimpleIntersectionTree simpleTree;
|
||||
IntersectionTree tree;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
Rect rect = Rect::XYWH(rand.nextRangeF(0, 500),
|
||||
rand.nextRangeF(0, 500),
|
||||
rand.nextRangeF(0, 200),
|
||||
rand.nextRangeF(0, 200));
|
||||
CHECK(tree.add(rect) == simpleTree.add({rect.left(),
|
||||
rect.top(),
|
||||
rect.right(),
|
||||
rect.bot()}));
|
||||
}
|
||||
}
|
||||
{
|
||||
SimpleIntersectionTree simpleTree;
|
||||
IntersectionTree tree;
|
||||
CHECK(tree.add(Rect(float2(-std::numeric_limits<float>::infinity()),
|
||||
float2(std::numeric_limits<float>::infinity()))));
|
||||
CHECK(!tree.add(Rect::WH(1,1)));
|
||||
CHECK(!tree.add(Rect::WH(1,std::numeric_limits<float>::infinity())));
|
||||
CHECK(tree.add(Rect::WH(0, 0)));
|
||||
CHECK(tree.add(Rect::WH(-1, 1)));
|
||||
CHECK(tree.add(Rect::WH(1, std::numeric_limits<float>::quiet_NaN())));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace skgpu
|
Loading…
Reference in New Issue
Block a user