blob: 28903b4d9fb26ccd2fdaa3483f69ab454886615b [file]
/*
* Copyright 2026 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef skgpu_graphite_sparse_strips_Polyline_DEFINED
#define skgpu_graphite_sparse_strips_Polyline_DEFINED
#include "include/core/SkScalar.h"
#include "include/private/base/SkTDArray.h"
#include "src/gpu/graphite/sparse_strips/SparseStripsTypes.h"
#include <cmath>
#include <utility>
namespace skgpu::graphite {
/*
* Helper class that uses the watertight property of flattened paths to deduplicate resulting
* points. Since any two adjacent points imply a continuous line segment, NaN values are injected
* into the underlying array as sentinels to signal the end of a sub-path. During tile creation, the
* raw index into the `fPoints` array is stored on the tile, allowing the coverage generation phase
* to retrieve the correct parent line independent of the deduplication.
*/
class Polyline {
public:
void reset() {
fPoints.clear();
}
void reserve(int count) {
fPoints.reserve(count);
}
void appendPoint(SkPoint pt) {
SkASSERT(!std::isnan(pt.fX) && !std::isnan(pt.fY));
if (fPoints.empty() || fPoints.back() != pt) {
fPoints.push_back(pt);
}
}
void appendSentinel() {
if (!fPoints.empty() && !std::isnan(fPoints.back().fX)) {
fPoints.push_back({SK_ScalarNaN, SK_ScalarNaN});
}
}
class LineIterator {
public:
LineIterator(const SkPoint* pts, int index, int count)
: fPts(pts), fCount(count), fIndex(index) {
this->advanceToNextValidIndex();
// Defensively call this to protect against malformed input. In ordinary usage, this
// should never be required.
this->advanceToValidLine();
}
std::pair<Line, int> operator*() const {
SkASSERT(fIndex + 1 < fCount);
return { {fPts[fIndex], fPts[fIndex + 1]}, fIndex };
}
LineIterator& operator++() {
fIndex++;
advanceToValidLine();
return *this;
}
bool operator!=(const LineIterator& other) const {
return fIndex != other.fIndex;
}
private:
SK_ALWAYS_INLINE void advanceToNextValidIndex() {
while (fIndex < fCount && std::isnan(fPts[fIndex].fX)) {
fIndex++;
}
}
SK_ALWAYS_INLINE void advanceToValidLine() {
while (fIndex + 1 < fCount) {
if (std::isnan(fPts[fIndex + 1].fX)) {
// If the next point in fPoints is a NaN, try to "leap-frog" over it. This will
// be the majority of cases. Else, walk the remaining points until a valid point
// or the end of the array is reached.
fIndex += 2;
// Does nothing if we're already at a valid index
this->advanceToNextValidIndex();
continue;
}
return;
}
fIndex = fCount;
}
const SkPoint* fPts;
const int fCount;
int fIndex;
};
LineIterator begin() const {
return LineIterator(fPoints.begin(), 0, fPoints.size());
}
LineIterator end() const {
return LineIterator(fPoints.begin(), fPoints.size(), fPoints.size());
}
bool empty() const { return fPoints.empty(); }
size_t count() const { return fPoints.size(); }
const SkTDArray<SkPoint>& points() const { return fPoints; }
private:
SkTDArray<SkPoint> fPoints;
};
} // namespace skgpu::graphite
#endif // skgpu_graphite_sparse_strips_Polyline_DEFINED