blob: 58dc7f2cedbf7b4849215c9a95715d471f6abc7b [file] [log] [blame]
/*
* Copyright 2025 Rive
*/
#ifdef @FRAGMENT
PLS_BLOCK_BEGIN
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
PLS_DECL4F(COLOR_PLANE_IDX, colorBuffer);
#endif
PLS_DECLUI(CLIP_PLANE_IDX, clipBuffer);
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
PLS_DECL4F(SCRATCH_COLOR_PLANE_IDX, scratchColorBuffer);
#endif
PLS_DECLUI(COVERAGE_PLANE_IDX, coverageBuffer);
PLS_BLOCK_END
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
PLS_FRAG_COLOR_MAIN(@drawFragmentMain)
#else
PLS_MAIN(@drawFragmentMain)
#endif
{
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
// Calculate fragment coverage before entering the interlock.
half fragCoverage =
#ifdef @DRAW_INTERIOR_TRIANGLES
v_windingWeight;
#else
find_frag_coverage(v_coverages);
#endif
half4 paintColor;
half maxCoverage;
#if defined(@DRAW_INTERIOR_TRIANGLES) && defined(@BORROWED_COVERAGE_PASS)
if (!@BORROWED_COVERAGE_PASS)
#endif
{
// Calculate the paint color before entering the interlock.
paintColor = find_paint_color(v_paint, 1. FRAGMENT_CONTEXT_UNPACK);
maxCoverage = 1.;
#ifdef @ENABLE_CLIP_RECT
// Calculate the clip rect before entering the interlock.
if (@ENABLE_CLIP_RECT)
{
half clipRectMin = min_value(cast_float4_to_half4(v_clipRect));
maxCoverage = min(clipRectMin, maxCoverage);
}
#endif
}
PLS_INTERLOCK_BEGIN;
#if defined(@DRAW_INTERIOR_TRIANGLES) && defined(@BORROWED_COVERAGE_PASS)
if (@BORROWED_COVERAGE_PASS)
{
// Interior triangles with borrowed coverage never write color. They're
// also always the first fragment of the path at their pixel, so just
// blindly write coverage and move on.
PLS_STOREUI(coverageBuffer,
packHalf2x16(make_half2(fragCoverage, v_pathID)));
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
PLS_PRESERVE_4F(colorBuffer);
#endif
}
else
#endif // !@DRAW_INTERIOR_TRIANGLES && @BORROWED_COVERAGE_PASS
{
half2 coverageData = unpackHalf2x16(PLS_LOADUI(coverageBuffer));
half coverageBufferID = coverageData.g;
half initialCoverage =
coverageBufferID == v_pathID ? coverageData.r : make_half(.0);
half finalCoverage =
#ifndef @DRAW_INTERIOR_TRIANGLES
is_stroke(v_coverages) ? max(initialCoverage, fragCoverage) :
#endif
initialCoverage + fragCoverage;
#ifdef @ENABLE_CLIPPING
if (@ENABLE_CLIPPING && v_clipIDs.x != .0)
{
half2 clipData = unpackHalf2x16(PLS_LOADUI(clipBuffer));
half clipBufferID = clipData.g;
half clip =
clipBufferID == v_clipIDs.x ? clipData.r : make_half(.0);
maxCoverage = min(clip, maxCoverage);
}
#endif
// Find the coverage delta (c0 -> c1) that this fragment will apply,
// where c0 is the coverage with which "paintColor" is already blended
// into the framebuffer, and c1 is the total coverage with which we
// *want* it to be blended after this fragment. The geometry is ordered
// such that if c1 > 0, c1 >= c0 as well.
maxCoverage = max(maxCoverage, .0);
half c0 = safe_clamp_for_mali(initialCoverage, .0, maxCoverage);
half c1 = safe_clamp_for_mali(finalCoverage, .0, maxCoverage);
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
half4 dstColorPremul = PLS_LOAD4F(colorBuffer);
#ifdef @ENABLE_ADVANCED_BLEND
if (@ENABLE_ADVANCED_BLEND)
{
// Don't bother with advanced blend until coverage becomes > 0. This
// way, cutout regions don't pay the cost of advanced blend.
if (v_blendMode != cast_uint_to_half(BLEND_SRC_OVER) && c1 != .0)
{
if (c0 == .0)
{
// This is the first fragment of the path to apply the blend
// mode, meaning, the current dstColor is the correct value
// we need to pass to advanced_color_blend(). Calculate the
// color-blended paint color before coverage. Coverage can
// be applied later as a simple src-over operation.
paintColor.rgb =
advanced_color_blend(paintColor.rgb,
dstColorPremul,
cast_half_to_ushort(v_blendMode));
// Normally we need to save the color-blended paint color
// for any future fragments at this same pixel because once
// we blend this fragment, the original dstColor will be
// destroyed. However, there are 2 exceptions:
//
// * No need to save the color-blended paint color if we're
// a
// (clockwise) interior triangle, because those are always
// guaranteed to be the final fragment of the path at a
// given pixel.
//
// * No need to save the color-blended paint color once
// coverage
// is maxed out, out because once it's maxed, any future
// fragments will effectively be no-ops (since c1 - c0 ==
// 0).
#ifndef @DRAW_INTERIOR_TRIANGLES
if (c1 < maxCoverage)
{
PLS_STORE4F(scratchColorBuffer, paintColor);
}
#endif
}
else
{
// This is not the first fragment of the path to apply the
// blend mode, meaning, the current dstColor is no longer
// the correct value we need to pass to
// advanced_color_blend(). Instead, the first fragment saved
// its result of advanced_color_blend() to the scratch
// buffer, which we can pull back up and use to apply our
// fragment's coverage contribution.
paintColor = PLS_LOAD4F(scratchColorBuffer);
PLS_PRESERVE_4F(scratchColorBuffer);
}
}
// GENERATE_PREMULTIPLIED_PAINT_COLORS is false when
// @ENABLE_ADVANCED_BLEND is defined because advanced blend needs
// unmultiplied colors. Premultiply alpha now.
paintColor.rgb *= paintColor.a;
}
#endif // @ENABLE_ADVANCED_BLEND
#endif // @FIXED_FUNCTION_COLOR_OUTPUT
// Emit a paint color whose post-src-over-blend result is algebraically
// equivalent to applying the c0 -> c1 coverage delta.
//
// NOTE: "max(, eps)" is just to avoid a divide by zero. When the
// denominator would be 0, c0 == 1, which also means c1 == 1, and there
// is no coverage to apply. Since c0 == c1 == 1, (c1 - c0) / eps == 0,
// which is the result we want in this case.
paintColor *=
(c1 - c0) / max(1. - c0 * paintColor.a, EPSILON_FP16_NON_DENORM);
#ifndef @DRAW_INTERIOR_TRIANGLES
// Update the coverage buffer with our final value if we aren't an
// interior triangle, because another fragment from this same path might
// come along at this pixel. The only exception is if we're src-over and
// fully opaque, because at that point the next fragment will
// effectively be a no-op (since any color blended with itself is a
// no-op).
#ifdef @ENABLE_ADVANCED_BLEND
// We can't skip the write for advanced blends either because they use
// the ID in the coverage buffer to detect the first fragment of the
// path for dst reads.
#define COVERAGE_UPDATE_OPTIONAL \
(!@ENABLE_ADVANCED_BLEND || \
v_blendMode == cast_uint_to_half(BLEND_SRC_OVER)) && \
paintColor.a >= 1.
#else
#define COVERAGE_UPDATE_OPTIONAL paintColor.a >= 1.
#endif
PLS_STOREUI_OPTIONAL_IF(
COVERAGE_UPDATE_OPTIONAL,
coverageBuffer,
packHalf2x16(make_half2(finalCoverage, v_pathID)));
#else // -> @DRAW_INTERIOR_TRIANGLES
PLS_PRESERVE_UI(coverageBuffer);
#endif
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
PLS_STORE4F_OPTIONAL_IF(paintColor.a == .0,
colorBuffer,
dstColorPremul * (1. - paintColor.a) +
paintColor);
#endif
}
PLS_PRESERVE_UI(clipBuffer);
PLS_INTERLOCK_END;
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
_fragColor = paintColor;
#endif
EMIT_PLS;
}
#endif // @FRAGMENT