blob: 3dd382dcc0827cccaa2685c3d2d58178ca36bfb2 [file]
/*
* Copyright 2026 Rive
*/
#ifdef @FRAGMENT
PLS_BLOCK_BEGIN
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
PLS_DECL4F(COLOR_PLANE_IDX, colorBuffer);
#endif
PLS_DECL4F_WRITEONLY(CLIP_PLANE_IDX, clipBuffer);
PLS_BLOCK_END
#ifdef @NESTED_CLIP_UPDATE_ONLY
FRAG_STORAGE_BUFFER_BLOCK_BEGIN
STORAGE_BUFFER_U32_ATOMIC(COVERAGE_BUFFER_IDX, CoverageBuffer, coverageBuffer);
FRAG_STORAGE_BUFFER_BLOCK_END
#endif
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
#define CLOCKWISE_ATOMIC_PLS_MAIN PLS_FRAG_COLOR_MAIN
#define EMIT_CLOCKWISE_ATOMIC_PLS(FRAG_COLOR) \
_fragColor = FRAG_COLOR; \
EMIT_PLS_AND_FRAG_COLOR
#else
#define CLOCKWISE_ATOMIC_PLS_MAIN PLS_MAIN
#define EMIT_CLOCKWISE_ATOMIC_PLS(FRAG_COLOR) \
PLS_STORE4F(colorBuffer, FRAG_COLOR); \
EMIT_PLS;
#endif
CLOCKWISE_ATOMIC_PLS_MAIN(@drawFragmentMain)
{
#ifdef @DRAW_INTERIOR_TRIANGLES
VARYING_UNPACK(v_windingWeight, half);
half fragCoverage = v_windingWeight;
#else
VARYING_UNPACK(v_coverages, COVERAGE_TYPE);
half fragCoverage = v_coverages.x;
#endif //@DRAW_INTERIOR_TRIANGLES
#ifdef @NESTED_CLIP_UPDATE_ONLY
if (@NESTED_CLIP_UPDATE_ONLY)
{
VARYING_UNPACK(v_coveragePlacement, uint2);
VARYING_UNPACK(v_coverageCoord, float2);
uint coveragePitch = v_coveragePlacement.y;
uint coverageIndex =
v_coveragePlacement.x +
swizzle_buffer_idx_32x32(uint2(floor(v_coverageCoord)),
coveragePitch);
uint preexistingCoverageValue =
STORAGE_BUFFER_LOAD(coverageBuffer, coverageIndex);
half pathCoverage;
if (fragCoverage >= 1. &&
(preexistingCoverageValue < uniforms.coverageBufferPrefix ||
preexistingCoverageValue >=
(uniforms.coverageBufferPrefix | CLOCKWISE_FILL_ZERO_VALUE)))
{
// The inverse path has reached a coverage of 1, meaning, the area
// we are erasing from the clip has reached 0.
pathCoverage = .0;
// No need to update the coverage buffer because the blend op is
// min() and we have bottomed out at 0 -- it doesn't matter what any
// future fragments do anymore.
}
else
{
// clockwiseAtomic nested clip updates take the inverse path as
// input.
half inversePathCoverage = fragCoverage;
half unappliedFragCoverage = fragCoverage;
if (preexistingCoverageValue < uniforms.coverageBufferPrefix)
{
// There was no borrowed coverage and we *might* be the first
// fragment of the path to touch this pixel. Attempt to write
// out our coverage with an atomicMax.
uint targetCoverageValue =
uniforms.coverageBufferPrefix |
(CLOCKWISE_FILL_ZERO_VALUE +
clockwise_atomic_coverage_delta_to_fixed(
abs(fragCoverage)));
uint coverageBeforeMax =
STORAGE_BUFFER_ATOMIC_MAX(coverageBuffer,
coverageIndex,
targetCoverageValue);
if (coverageBeforeMax <= uniforms.coverageBufferPrefix)
{
// Success! We were the first fragment of the path at this
// pixel.
unappliedFragCoverage = .0; // We're done.
}
else if (coverageBeforeMax < targetCoverageValue)
{
// We were not first fragment of the path at this pixel, AND
// our atomicMax had some effect, but did not fully apply
// our coverage.
unappliedFragCoverage =
clockwise_atomic_fixed_to_coverage(coverageBeforeMax);
}
}
if (unappliedFragCoverage > .0)
{
// Coverage wasn't fully applied during the implicit clear
// operations above. Apply it now.
uint coverageBeforeAdd = STORAGE_BUFFER_ATOMIC_ADD(
coverageBuffer,
coverageIndex,
clockwise_atomic_coverage_delta_to_fixed(
abs(unappliedFragCoverage)));
inversePathCoverage =
clockwise_atomic_fixed_to_coverage(coverageBeforeAdd) +
fragCoverage;
}
// clockwiseAtomic nested clip updates take the inverse path as
// input, so take 1 - inversePathCoverageto get back to the original
// nested path.
pathCoverage = 1. - inversePathCoverage;
}
PLS_STORE4F(clipBuffer, make_half4(pathCoverage));
// Since the blend op is min(), emitting a color of 1 is effectively a
// no-op as long as we're using unorm targets.
EMIT_CLOCKWISE_ATOMIC_PLS(make_half4(1.))
}
else
#endif
{
PLS_STORE4F(clipBuffer, make_half4(fragCoverage));
EMIT_CLOCKWISE_ATOMIC_PLS(make_half4(.0))
}
}
#endif // FRAGMENT