blob: 6cf5a11791012dbf7558eb21019447e1914ef5d4 [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.
*/
#include "tests/graphite/sparse_strips/Oracle.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "src/core/SkCubics.h"
#include "src/core/SkQuads.h"
#include "src/core/SkVx.h"
#include "src/gpu/graphite/sparse_strips/MSAA_LUT.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <vector>
namespace skgpu::graphite {
void ScanlineOracle8x::intersectLine(const SkPoint pts[2], const skvx::float8& targetY) {
float dy = pts[1].fY - pts[0].fY;
if (dy != 0.0f) {
float invDy = 1.0f / dy;
float dx = pts[1].fX - pts[0].fX;
int32_t dir = dy > 0.0f ? 1 : -1;
skvx::float8 t = (targetY - pts[0].fY) * invDy;
auto valid = (t >= 0.0f) & (t < 1.0f);
if (any(valid)) {
CurveIntersection hit;
hit.x = pts[0].fX + t * dx;
hit.dir = if_then_else(valid, skvx::int8(dir), skvx::int8(0));
fHits.push_back(hit);
}
}
}
void ScanlineOracle8x::intersectQuad(const SkPoint pts[3], const skvx::float8& targetY) {
double Ay = pts[2].fY - 2.0 * pts[1].fY + pts[0].fY;
double By = 2.0 * (pts[1].fY - pts[0].fY);
double Cy = pts[0].fY;
double Ax = pts[2].fX - 2.0 * pts[1].fX + pts[0].fX;
double Bx = 2.0 * (pts[1].fX - pts[0].fX);
double Cx = pts[0].fX;
CurveIntersection hits[2];
for (int r = 0; r < 2; ++r) {
hits[r].x = 0.0f;
hits[r].dir = 0;
}
for (int k = 0; k < 8; ++k) {
double roots[2];
int numRoots = SkQuads::RootsReal(Ay, By, Cy - targetY[k], roots);
int validCount = 0;
for (int i = 0; i < numRoots; ++i) {
double t = roots[i];
if (t >= 0.0 && t < 1.0) {
float derivativeY = static_cast<float>(2.0 * Ay * t + By);
if (std::abs(derivativeY) > 1e-5f) {
float x = static_cast<float>(Ax * t * t + Bx * t + Cx);
int32_t dir = derivativeY > 0.0f ? 1 : -1;
if (validCount < 2) {
hits[validCount].x[k] = x;
hits[validCount].dir[k] = dir;
validCount++;
}
}
}
}
}
for (int r = 0; r < 2; ++r) {
if (any(hits[r].dir != 0)) {
fHits.push_back(hits[r]);
}
}
}
void ScanlineOracle8x::intersectConic(const SkPoint pts[3], float weight,
const skvx::float8& targetY) {
CurveIntersection hits[2];
for (int r = 0; r < 2; ++r) {
hits[r].x = 0.0f;
hits[r].dir = 0;
}
for (int k = 0; k < 8; ++k) {
double d0 = pts[0].fY - targetY[k];
double d1 = pts[1].fY - targetY[k];
double d2 = pts[2].fY - targetY[k];
double Ay = d2 - 2.0 * weight * d1 + d0;
double By = 2.0 * (weight * d1 - d0);
double Cy = d0;
double roots[2];
int numRoots = SkQuads::RootsReal(Ay, By, Cy, roots);
int validCount = 0;
for (int i = 0; i < numRoots; ++i) {
double t = roots[i];
if (t >= 0.0 && t < 1.0) {
double denom = (1.0 - t) * (1.0 - t) + 2.0 * weight * t * (1.0 - t) + t * t;
if (denom != 0.0) {
double numX = (1.0 - t) * (1.0 - t) * pts[0].fX +
2.0 * weight * t * (1.0 - t) * pts[1].fX +
t * t * pts[2].fX;
float x = static_cast<float>(numX / denom);
// denom is always positive, so we only need the numerator to find the sign
float derivativeY = static_cast<float>(2.0 * Ay * t + By);
if (std::abs(derivativeY) > 1e-5f) {
int32_t dir = derivativeY > 0.0f ? 1 : -1;
if (validCount < 2) {
hits[validCount].x[k] = x;
hits[validCount].dir[k] = dir;
validCount++;
}
}
}
}
}
}
for (int r = 0; r < 2; ++r) {
if (any(hits[r].dir != 0)) {
fHits.push_back(hits[r]);
}
}
}
void ScanlineOracle8x::intersectCubic(const SkPoint pts[4], const skvx::float8& targetY) {
double Ay = pts[3].fY - 3.0 * pts[2].fY + 3.0 * pts[1].fY - pts[0].fY;
double By = 3.0 * (pts[2].fY - 2.0 * pts[1].fY + pts[0].fY);
double Cy = 3.0 * (pts[1].fY - pts[0].fY);
double Dy = pts[0].fY;
double Ax = pts[3].fX - 3.0 * pts[2].fX + 3.0 * pts[1].fX - pts[0].fX;
double Bx = 3.0 * (pts[2].fX - 2.0 * pts[1].fX + pts[0].fX);
double Cx = 3.0 * (pts[1].fX - pts[0].fX);
double Dx = pts[0].fX;
CurveIntersection hits[3];
for (int r = 0; r < 3; ++r) {
hits[r].x = 0.0f;
hits[r].dir = 0;
}
for (int k = 0; k < 8; ++k) {
double roots[3];
int numRoots = SkCubics::RootsReal(Ay, By, Cy, Dy - targetY[k], roots);
int validCount = 0;
for (int i = 0; i < numRoots; ++i) {
double t = roots[i];
if (t >= 0.0 && t < 1.0) {
float x = static_cast<float>(SkCubics::EvalAt(Ax, Bx, Cx, Dx, t));
float derivativeY = static_cast<float>(3.0 * Ay * t * t + 2.0 * By * t + Cy);
if (std::abs(derivativeY) > 1e-5f) {
int32_t dir = derivativeY > 0.0f ? 1 : -1;
if (validCount < 3) {
hits[validCount].x[k] = x;
hits[validCount].dir[k] = dir;
validCount++;
}
}
}
}
}
for (int r = 0; r < 3; ++r) {
if (any(hits[r].dir != 0)) {
fHits.push_back(hits[r]);
}
}
}
void ScanlineOracle8x::collectPathIntersections(const SkPath& path, const skvx::float8& targetY) {
fHits.clear();
SkPath::Iter iter(path, /*forceClose=*/true);
SkPoint pts[4];
SkPath::Verb verb;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kLine_Verb:
this->intersectLine(pts, targetY);
break;
case SkPath::kQuad_Verb:
this->intersectQuad(pts, targetY);
break;
case SkPath::kConic_Verb:
this->intersectConic(pts, iter.conicWeight(), targetY);
break;
case SkPath::kCubic_Verb:
this->intersectCubic(pts, targetY);
break;
default:
break;
}
}
}
skvx::int8 ScanlineOracle8x::evaluatePixelWinding(float px) const {
SkASSERT(fBuilt);
static const skvx::float8 kSubX =
(skvx::cast<float>(skvx::byte8::Load(kMsaaPattern<uint8_t>.data())) + 0.5f) / 8.0f;
skvx::float8 vPx = px + kSubX;
skvx::int8 pixelWinding(0);
for (const auto& hit : fHits) {
pixelWinding += if_then_else(vPx >= hit.x, hit.dir, skvx::int8(0));
}
return pixelWinding;
}
void ScanlineOracle8x::buildRow(int py, const SkPath& path) {
fPath = path;
fCurrentRow = py;
fHits.clear();
fIntervals.clear();
static const skvx::float8 kSubY =
(skvx::float8{0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f} + 0.5f) / 8.0f;
skvx::float8 vPy = static_cast<float>(py) + kSubY;
this->collectPathIntersections(path, vPy);
fBuilt = true;
if (!fHits.empty()) {
SkRect bounds = path.getBounds();
int minX = static_cast<int>(std::floor(bounds.fLeft));
int maxX = static_cast<int>(std::ceil(bounds.fRight));
skvx::int8 lastWinding(0);
bool hasLast = false;
// TODO (thomsmit): Currently, we do not sort the raycast hits and instead perform an O(n)
// search to sum all subsample points left of the current pixel position. This is because
// there is no guarantee that subsamples will be crossed in the same pixel. Moving to a
// sorting based technique would require N subsample vecs, and sorting across each of them.
for (int px = minX; px <= maxX; ++px) {
skvx::int8 w = this->evaluatePixelWinding(static_cast<float>(px));
if (!hasLast || any(w != lastWinding)) {
fIntervals.push_back({px, w});
lastWinding = w;
hasLast = true;
}
}
// Append terminator at maxX + 1 to define the upper bound of the final span [x_k, maxX + 1)
fIntervals.push_back({maxX + 1, skvx::int8(0)});
}
}
void ScanlineOracle8x::buildRow(int py) {
this->buildRow(py, fPath);
}
skvx::int8 ScanlineOracle8x::evaluate(SkPoint pt) {
return this->evaluate(pt, fPath);
}
skvx::int8 ScanlineOracle8x::evaluate(SkPoint pt, const SkPath& path) {
int py = static_cast<int>(std::floor(pt.fY));
if (!fBuilt || fCurrentRow != py || fPath != path) {
this->buildRow(py, path);
}
return this->evaluatePixelWinding(pt.fX);
}
} // namespace skgpu::graphite