| /* |
| * 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 |