blob: 6b7f4b7641ab030dd17795adcf391f38d24faf9f [file] [log] [blame]
/*
* Copyright 2023 Rive
*/
#ifdef @FRAGMENT
FRAG_STORAGE_BUFFER_BLOCK_BEGIN
STORAGE_BUFFER_U32x2(PAINT_BUFFER_IDX, PaintBuffer, @paintBuffer);
STORAGE_BUFFER_F32x4(PAINT_AUX_BUFFER_IDX, PaintAuxBuffer, @paintAuxBuffer);
STORAGE_BUFFER_U32_ATOMIC(COVERAGE_BUFFER_IDX, CoverageBuffer, coverageBuffer);
FRAG_STORAGE_BUFFER_BLOCK_END
#ifdef @BORROWED_COVERAGE_PASS
INLINE void apply_borrowed_coverage(half borrowedCoverage, uint coverageIndex)
{
// Try to apply borrowedCoverage, assuming the existing coverage value
// is zero.
uint borrowedCoverageFixed =
uint(abs(borrowedCoverage) * CLOCKWISE_COVERAGE_PRECISION + .5);
uint targetCoverageValue =
uniforms.coverageBufferPrefix |
(CLOCKWISE_FILL_ZERO_VALUE - borrowedCoverageFixed);
uint coverageBeforeMax = STORAGE_BUFFER_ATOMIC_MAX(coverageBuffer,
coverageIndex,
targetCoverageValue);
if (coverageBeforeMax >= uniforms.coverageBufferPrefix)
{
// Coverage was not zero. Undo the atomicMax and then subtract
// borrowedCoverageFixed this time.
uint undoAtomicMax =
coverageBeforeMax - max(coverageBeforeMax, targetCoverageValue);
STORAGE_BUFFER_ATOMIC_ADD(coverageBuffer,
coverageIndex,
undoAtomicMax - borrowedCoverageFixed);
}
}
#endif
INLINE void apply_stroke_coverage(INOUT(float) paintAlpha,
half fragCoverage,
uint coverageIndex)
{
if (min(paintAlpha, fragCoverage) >= 1.)
{
// Solid stroke pixels don't need to work out coverage at all. We can
// just blast them out without ever touching the coverage buffer.
return;
}
half X;
uint fragCoverageFixed =
uint(abs(fragCoverage) * CLOCKWISE_COVERAGE_PRECISION + .5);
uint coverageBeforeMax = STORAGE_BUFFER_ATOMIC_MAX(
coverageBuffer,
coverageIndex,
uniforms.coverageBufferPrefix | fragCoverageFixed);
if (coverageBeforeMax < uniforms.coverageBufferPrefix)
{
// This is the first fragment of the stroke to touch this pixel. Just
// multiply in our coverage and write it out.
X = fragCoverage;
}
else
{
// This pixel has been touched previously by a fragment in the stroke.
// Multiply in an incremental coverage value that mixes with what's
// already in the framebuffer.
half c1 =
cast_uint_to_half(coverageBeforeMax & CLOCKWISE_COVERAGE_MASK) *
CLOCKWISE_COVERAGE_INVERSE_PRECISION;
half c2 = max(c1, fragCoverage);
X = (c2 - c1) / (1. - c1 * paintAlpha);
}
paintAlpha *= X;
}
INLINE void apply_fill_coverage(INOUT(float) paintAlpha,
half fragCoverageRemaining,
uint coverageIndex)
{
uint coverageInitialValue =
STORAGE_BUFFER_LOAD(coverageBuffer, coverageIndex);
if (min(paintAlpha, fragCoverageRemaining) >= 1. &&
(coverageInitialValue < uniforms.coverageBufferPrefix ||
coverageInitialValue >=
(uniforms.coverageBufferPrefix | CLOCKWISE_FILL_ZERO_VALUE)))
{
// If we're solid, AND the current coverage at this pixel is >= 0, then
// we can just write out or color without working out coverage any
// further.
return;
}
half X = .0; // Amount by which to multiply paintAlpha.
uint fragCoverageRemainingFixed =
uint(abs(fragCoverageRemaining) * CLOCKWISE_COVERAGE_PRECISION + .5);
if (coverageInitialValue < uniforms.coverageBufferPrefix)
{
// The initial coverage value does not belong to this path. We *might*
// be the first fragment of the path to touch this pixel. Attempt to
// write out our coverage with an atomicMax.
uint targetCoverage =
uniforms.coverageBufferPrefix |
(CLOCKWISE_FILL_ZERO_VALUE + fragCoverageRemainingFixed);
uint coverageBeforeMax = STORAGE_BUFFER_ATOMIC_MAX(coverageBuffer,
coverageIndex,
targetCoverage);
if (coverageBeforeMax <= uniforms.coverageBufferPrefix)
{
// Success! We were the first fragment of the path at this pixel.
X = fragCoverageRemaining; // Just multiply paintAlpha by coverage.
#ifdef @DRAW_INTERIOR_TRIANGLES
X = min(X, 1.);
#endif
fragCoverageRemaining = .0; // We're done.
}
else if (coverageBeforeMax < targetCoverage)
{
// We were not first fragment of the path at this pixel, AND our
// atomicMax had an effect that we now have to account for in
// paintAlpha. Coverage increased from "coverageBeforeMax" to
// "fragCoverageRemaining".
//
// NOTE: because we know coverage was initially zero, and because
// coverage is always positive in this pass, we know
// coverageBeforeMax >= 0.
uint c1Fixed = (coverageBeforeMax & CLOCKWISE_COVERAGE_MASK) -
CLOCKWISE_FILL_ZERO_VALUE;
half c1 = cast_uint_to_half(c1Fixed) *
CLOCKWISE_COVERAGE_INVERSE_PRECISION;
half c2 = fragCoverageRemaining;
#ifdef @DRAW_INTERIOR_TRIANGLES
c2 = min(c2, 1.);
#endif
// Apply the coverage increase from the atomicMax here. The next
// step will apply the remaining increase.
X = (c2 - c1) / (1. - c1 * paintAlpha);
// We increased coverage by an amount of "fragCoverageRemaining" -
// "coverageBeforeMax". However, we wanted to increase coverage by
// "fragCoverageRemaining". So the remaining amount we still need to
// increase by is "coverageBeforeMax".
fragCoverageRemainingFixed = c1Fixed;
fragCoverageRemaining = c1;
}
}
if (fragCoverageRemaining > .0)
{
// At this point we know the value in the coverage buffer belongs to
// this path, so we can do a simple atomicAdd.
uint coverageBeforeAdd =
STORAGE_BUFFER_ATOMIC_ADD(coverageBuffer,
coverageIndex,
fragCoverageRemainingFixed);
half c1 =
cast_int_to_half(int((coverageBeforeAdd & CLOCKWISE_COVERAGE_MASK) -
CLOCKWISE_FILL_ZERO_VALUE)) *
CLOCKWISE_COVERAGE_INVERSE_PRECISION;
half c2 = c1 + fragCoverageRemaining;
c1 = clamp(c1, .0, 1.);
c2 = clamp(c2, .0, 1.);
// Apply the coverage increase from c1 -> c2 that we just did, in
// addition to any coverage that had been applied previously.
half one_minus_c1a = 1. - c1 * paintAlpha;
if (one_minus_c1a <= .0)
discard;
X += (1. - X * paintAlpha) * (c2 - c1) / one_minus_c1a;
}
paintAlpha *= X;
}
FRAG_DATA_MAIN(half4, @drawFragmentMain)
{
VARYING_UNPACK(v_paint, float4);
#ifdef @DRAW_INTERIOR_TRIANGLES
VARYING_INIT(v_windingWeight, half);
#else
VARYING_INIT(v_coverages, COVERAGE_TYPE);
#endif //@DRAW_INTERIOR_TRIANGLES
VARYING_UNPACK(v_pathID, half);
#ifdef @ENABLE_CLIPPING
VARYING_UNPACK(v_clipIDs, half2);
#endif
#ifdef @ENABLE_CLIP_RECT
VARYING_UNPACK(v_clipRect, float4);
#endif
#ifdef @ENABLE_ADVANCED_BLEND
VARYING_UNPACK(v_blendMode, half);
#endif
VARYING_UNPACK(v_coveragePlacement, uint2);
VARYING_UNPACK(v_coverageCoord, float2);
half4 paintColor;
#if defined(@DRAW_INTERIOR_TRIANGLES) && defined(@BORROWED_COVERAGE_PASS)
if (!@BORROWED_COVERAGE_PASS)
#endif
{
paintColor = find_paint_color(v_paint, 1. FRAGMENT_CONTEXT_UNPACK);
}
if (paintColor.a == .0)
{
discard;
}
half fragCoverage =
#ifdef @DRAW_INTERIOR_TRIANGLES
v_windingWeight;
#else
find_frag_coverage(v_coverages);
#endif
// Swizzle the coverage buffer in a tiled format, starting with 32x32
// row-major tiles.
uint coverageIndex = v_coveragePlacement.x;
uint coveragePitch = v_coveragePlacement.y;
uint2 coverageCoord = uint2(floor(v_coverageCoord));
coverageIndex += (coverageCoord.y >> 5) * (coveragePitch << 5) +
(coverageCoord.x >> 5) * (32 << 5);
// Subdivide each main tile into 4x4 column-major tiles.
coverageIndex += ((coverageCoord.x & 0x1f) >> 2) * (32 << 2) +
((coverageCoord.y & 0x1f) >> 2) * (4 << 2);
// Let the 4x4 tiles be row-major.
coverageIndex += (coverageCoord.y & 0x3) * 4 + (coverageCoord.x & 0x3);
#ifdef @BORROWED_COVERAGE_PASS
if (@BORROWED_COVERAGE_PASS)
{
apply_borrowed_coverage(-fragCoverage, coverageIndex);
discard;
}
#endif // BORROWED_COVERAGE_PASS
#ifndef @DRAW_INTERIOR_TRIANGLES
if (is_stroke(v_coverages))
{
fragCoverage = clamp(fragCoverage, .0, 1.);
apply_stroke_coverage(paintColor.a, fragCoverage, coverageIndex);
}
else // It's a fill.
#endif // !DRAW_INTERIOR_TRIANGLES
{
apply_fill_coverage(paintColor.a, fragCoverage, coverageIndex);
}
EMIT_FRAG_DATA(paintColor);
}
#endif // FRAGMENT