Add performance enhancements for SkRegion::op

When looking at pprof for a particularly gnarly set of
consecutive ops (see new benchmark), a curious hotspot
was distance_to_sentinel. We can look at the item just
before the current run to see how long it is instead of
using a loop to walk
the entire way. This had a ~15% improvement.

Before:
```
$ ./control_nanobench --match region
curr/maxrss	loops	min	median	mean	max	stddev	samples   	config	bench
  91/88  MB	497	16.2ns	16.2ns	16.2ns	16.2ns	0%	█▁▁▁▁▁▁▁▁▃	nonrendering	region_contains_sect
  92/88  MB	1	1.37s	1.46s	1.5s	1.77s	8%	▁▃▄▄█▅▂▁▃▂	nonrendering	region_checkerboard
  91/88  MB	1	1.23ms	1.26ms	1.27ms	1.35ms	3%	█▃▂▄▁▁▅▁▁▆	nonrendering	region_setRects_10000_sorted
  91/88  MB	1	301µs	301µs	302µs	307µs	1%	█▂▂█▁▁▁▁▁▁	nonrendering	region_setRects_2500_sorted
  91/88  MB	4	59.1µs	59.1µs	60.1µs	69.1µs	5%	█▁▁▁▁▁▁▁▁▁	nonrendering	region_setRects_500_sorted
  91/88  MB	42	5.63µs	5.65µs	5.74µs	6.59µs	5%	▁▁▁▁▁▁█▁▁▁	nonrendering	region_setRects_50_sorted
  91/88  MB	1	5.5ms	5.51ms	5.52ms	5.55ms	0%	█▂▃█▂▁▄▁█▃	nonrendering	region_setRects_10000
  91/88  MB	1	1.37ms	1.39ms	1.39ms	1.42ms	1%	▇▁▄▇▂▃█▁▁▇	nonrendering	region_setRects_2500
  91/88  MB	2	266µs	266µs	268µs	285µs	2%	▂▂▁▁█▁▁▁▁▁	nonrendering	region_setRects_500
  91/88  MB	12	16.9µs	16.9µs	17.6µs	22.5µs	10%	▂▁▁▁█▁▁▁▁▂	nonrendering	region_setRects_50

```

After:
```
$ ./nanobench --match region
curr/maxrss	loops	min	median	mean	max	stddev	samples   	config	bench
  91/88  MB	394	16.2ns	16.2ns	16.2ns	16.5ns	1%	█▂▁▂▁▂▂▂▂▂	nonrendering	region_contains_sect
  92/88  MB	1	1.07s	1.1s	1.12s	1.26s	7%	▃▂█▄█▁▁▁▁▂	nonrendering	region_checkerboard
  92/88  MB	1	1.08ms	1.09ms	1.11ms	1.16ms	2%	█▂▆▂▁▆▂▂▁▅	nonrendering	region_setRects_10000_sorted
  91/88  MB	2	266µs	268µs	270µs	285µs	2%	▁▃▂▂▁█▁▃▁▂	nonrendering	region_setRects_2500_sorted
  91/88  MB	5	52µs	52.3µs	53.1µs	59.1µs	4%	▂▁▁▁█▃▁▁▁▂	nonrendering	region_setRects_500_sorted
  91/88  MB	49	5µs	5.01µs	5.03µs	5.19µs	1%	▂▁▁▁▁▁▁█▂▂	nonrendering	region_setRects_50_sorted
  91/88  MB	1	4.85ms	4.86ms	4.87ms	4.91ms	0%	▄▃▃▆▂▁▂█▁▁	nonrendering	region_setRects_10000
  91/88  MB	1	1.22ms	1.22ms	1.23ms	1.26ms	1%	█▁▁█▂▁▂█▂▂	nonrendering	region_setRects_2500
  91/88  MB	2	238µs	239µs	246µs	282µs	6%	▆▁▁▁▁▁▁▁█▁	nonrendering	region_setRects_500
  91/88  MB	13	14.6µs	14.7µs	14.8µs	15.6µs	2%	█▂▁▄▁▁▁▁▁▁	nonrendering	region_setRects_50

```

Bug: 511952524
Change-Id: I9c496ba621c61e62790cca62793de1fefee3a85e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1235636
Auto-Submit: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Florin Malita <fmalita@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
diff --git a/bench/RegionBench.cpp b/bench/RegionBench.cpp
index 7c887d0..74c12038 100644
--- a/bench/RegionBench.cpp
+++ b/bench/RegionBench.cpp
@@ -180,3 +180,33 @@
 DEF_BENCH(return new RegionSetRectsBench(500, true);)
 DEF_BENCH(return new RegionSetRectsBench(2500, true);)
 DEF_BENCH(return new RegionSetRectsBench(10000, true);)
+
+class RegionCheckerboardBench : public Benchmark {
+public:
+    RegionCheckerboardBench() {}
+
+protected:
+    const char* onGetName() override { return "region_checkerboard"; }
+
+    bool isSuitableFor(Backend backend) override {
+        return backend == Backend::kNonRendering;
+    }
+
+    void onDraw(int loops, SkCanvas* canvas) override {
+        for (int i = 0; i < loops; ++i) {
+            SkRegion rgn;
+            // Simulate a 300x200 grid of tiles where every other tile is missing.
+            // This creates 30,000 non-merging rectangles with high coordinate values
+            // similar to b/511952524.
+            for (int y = 0; y < 300; ++y) {
+                for (int x = 0; x < 200; ++x) {
+                    if ((x + y) % 2 == 0) {
+                        rgn.op(SkIRect::MakeXYWH(x * 256, y * 256, 256, 256), SkRegion::kUnion_Op);
+                    }
+                }
+            }
+        }
+    }
+};
+
+DEF_BENCH(return new RegionCheckerboardBench();)
diff --git a/src/core/SkRegion.cpp b/src/core/SkRegion.cpp
index effae18..59cf109 100644
--- a/src/core/SkRegion.cpp
+++ b/src/core/SkRegion.cpp
@@ -798,10 +798,20 @@
     }
 };
 
+/**
+ *  Returns the distance to the next X-sentinel.
+ *
+ *  The runs pointer points to the first interval [Left, Right].
+ *  The value at runs[-1] is the number of intervals for this scanline.
+ *  This is safe because the caller has already advanced the runs pointer
+ *  past the interval-count.
+ *
+ *  The scanline layout is: [Bottom-Y] [IntervalCount] [L1, R1, L2, R2, ...] [X-Sentinel]
+ */
 static int distance_to_sentinel(const SkRegionPriv::RunType* runs) {
-    const SkRegionPriv::RunType* ptr = runs;
-    while (*ptr != SkRegion_kRunTypeSentinel) { ptr += 2; }
-    return ptr - runs;
+    int intervals = runs[-1];
+    SkASSERT_RELEASE(runs[intervals * 2] == SkRegion_kRunTypeSentinel);
+    return intervals * 2;
 }
 
 static int operate_on_span(const SkRegionPriv::RunType a_runs[],