run method
Returns linear RGBA for attachment zero. Null discards the fragment.
Implementation
@override
Vector4? run(Float32List v, ShaderBindings bindings, FragmentContext c) {
final source = bindings.textures['source_texture'];
if (source == null) return Vector4(0.0, 0.0, 0.0, 1.0);
final params = bindings.vec4(
'FxaaInfo',
'params',
Vector4(0.0, 0.0, 0.125, 0.75),
);
final middle = source.sample(v[0], v[1]);
final mid = _weight(middle);
// Colours kept rather than only their weights: `Sharpen` below needs the
// neighbourhood, and these are the same four taps either way.
final northRgb = source.sample(v[0], v[1] - params.y);
final southRgb = source.sample(v[0], v[1] + params.y);
final westRgb = source.sample(v[0] - params.x, v[1]);
final eastRgb = source.sample(v[0] + params.x, v[1]);
final north = _weight(northRgb);
final south = _weight(southRgb);
final west = _weight(westRgb);
final east = _weight(eastRgb);
final sharpening = bindings.vec4('FxaaInfo', 'sharpen', Vector4.zero());
final sharpen = sharpening.x;
final robust = sharpening.y > 0.5;
final lowest = math.min(
mid,
math.min(math.min(north, south), math.min(west, east)),
);
final highest = math.max(
mid,
math.max(math.max(north, south), math.max(west, east)),
);
final contrast = highest - lowest;
if (contrast < math.max(0.0312, highest * params.z)) {
return _sharpen(
middle,
northRgb,
southRgb,
westRgb,
eastRgb,
sharpen,
robust: robust,
);
}
// FXAA 3.11 Quality, step for step; see the GLSL.
double weightAt(double x, double y) => _weight(source.sample(x, y));
final northWest = weightAt(v[0] - params.x, v[1] - params.y);
final southEast = weightAt(v[0] + params.x, v[1] + params.y);
final northEast = weightAt(v[0] + params.x, v[1] - params.y);
final southWest = weightAt(v[0] - params.x, v[1] + params.y);
final edgeHorizontal =
(northWest + southWest - 2.0 * west).abs() +
2.0 * (north + south - 2.0 * mid).abs() +
(northEast + southEast - 2.0 * east).abs();
final edgeVertical =
(northWest + northEast - 2.0 * north).abs() +
2.0 * (west + east - 2.0 * mid).abs() +
(southWest + southEast - 2.0 * south).abs();
final horizontalSpan = edgeHorizontal >= edgeVertical;
final lowPass =
(2.0 * (north + south + west + east) +
northWest +
northEast +
southWest +
southEast) /
12.0;
final subpixC = ((lowPass - mid).abs() / contrast).clamp(0.0, 1.0);
final subpixF = (3.0 - 2.0 * subpixC) * subpixC * subpixC;
final subpixH = subpixF * subpixF * params.w;
final lumaN = horizontalSpan ? north : west;
final lumaS = horizontalSpan ? south : east;
final gradientN = lumaN - mid;
final gradientS = lumaS - mid;
final pairN = gradientN.abs() >= gradientS.abs();
final gradient = math.max(gradientN.abs(), gradientS.abs());
final texelAcross = horizontalSpan ? params.y : params.x;
final lengthSign = pairN ? -texelAcross : texelAcross;
final pairAverage = 0.5 * (pairN ? lumaN + mid : lumaS + mid);
// The edge search, along x for a horizontal span and y for a vertical.
final alongX = horizontalSpan ? params.x : 0.0;
final alongY = horizontalSpan ? 0.0 : params.y;
final startX = v[0] + (horizontalSpan ? 0.0 : lengthSign * 0.5);
final startY = v[1] + (horizontalSpan ? lengthSign * 0.5 : 0.0);
final gradientScaled = gradient * 0.25;
var posNX = startX - alongX;
var posNY = startY - alongY;
var posPX = startX + alongX;
var posPY = startY + alongY;
var endN = weightAt(posNX, posNY) - pairAverage;
var endP = weightAt(posPX, posPY) - pairAverage;
var doneN = endN.abs() >= gradientScaled;
var doneP = endP.abs() >= gradientScaled;
for (var i = 1; i < _searchSteps.length; i++) {
if (doneN && doneP) break;
final stride = _searchSteps[i];
if (!doneN) {
posNX -= alongX * stride;
posNY -= alongY * stride;
endN = weightAt(posNX, posNY) - pairAverage;
doneN = endN.abs() >= gradientScaled;
}
if (!doneP) {
posPX += alongX * stride;
posPY += alongY * stride;
endP = weightAt(posPX, posPY) - pairAverage;
doneP = endP.abs() >= gradientScaled;
}
}
final distanceN = horizontalSpan ? v[0] - posNX : v[1] - posNY;
final distanceP = horizontalSpan ? posPX - v[0] : posPY - v[1];
final middleBelow = mid - pairAverage < 0.0;
final nearerN = distanceN < distanceP;
final goodSpan = nearerN
? (endN < 0.0) != middleBelow
: (endP < 0.0) != middleBelow;
final nearest = math.min(distanceN, distanceP);
final pixelOffset = 0.5 - nearest / (distanceN + distanceP);
final offset = math.max(goodSpan ? pixelOffset : 0.0, subpixH);
final out = horizontalSpan
? source.sample(v[0], v[1] + offset * lengthSign)
: source.sample(v[0] + offset * lengthSign, v[1]);
return _sharpen(
out,
northRgb,
southRgb,
westRgb,
eastRgb,
sharpen,
robust: robust,
);
}