| /* |
| * Copyright 2023 Rive |
| */ |
| |
| #ifdef @FRAGMENT |
| |
| PLS_BLOCK_BEGIN |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| PLS_DECL4F(COLOR_PLANE_IDX, colorBuffer); |
| #endif |
| PLS_DECL4F(CLIP_PLANE_IDX, clipBuffer); |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| PLS_DECL4F_RGB10_A2_ATOMIC(SCRATCH_COLOR_PLANE_IDX, blendColorBuffer); |
| #endif |
| PLS_BLOCK_END |
| |
| FRAG_STORAGE_BUFFER_BLOCK_BEGIN |
| STORAGE_BUFFER_U32_ATOMIC(COVERAGE_BUFFER_IDX, CoverageBuffer, coverageBuffer); |
| FRAG_STORAGE_BUFFER_BLOCK_END |
| |
| INLINE void apply_stroke_coverage(INOUT(float) paintAlpha, |
| half fragCoverage, |
| uint coverageIndex, |
| OUT(uint) preexistingCoverageValue, |
| OUT(half) newCoverage) |
| { |
| #ifdef @FIXED_FUNCTION_COLOR_OUTPUT |
| 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, even |
| // if another fragment from the path will get drawn on top. This is |
| // because any fragment drawn on top will be the same color, and any |
| // color blended onto a fully opaque version of itself is a no-op. |
| return; |
| } |
| #endif |
| |
| half X; |
| uint fragCoverageFixed = |
| uint(abs(fragCoverage) * CLOCKWISE_COVERAGE_PRECISION + .5); |
| preexistingCoverageValue = STORAGE_BUFFER_ATOMIC_MAX( |
| coverageBuffer, |
| coverageIndex, |
| uniforms.coverageBufferPrefix | fragCoverageFixed); |
| if (preexistingCoverageValue < 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; |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| newCoverage = fragCoverage; |
| #endif |
| } |
| else |
| { |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| if ((preexistingCoverageValue & BLEND_COLOR_VALID_BIT) != 0u) |
| { |
| // The BLEND_COLOR_VALID_BIT had already been set at this fragment. |
| // Redo the atomic max with that bit set. |
| preexistingCoverageValue = STORAGE_BUFFER_ATOMIC_MAX( |
| coverageBuffer, |
| coverageIndex, |
| uniforms.coverageBufferPrefix | BLEND_COLOR_VALID_BIT | |
| fragCoverageFixed); |
| } |
| #endif |
| // 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 c0 = cast_uint_to_half(preexistingCoverageValue & |
| CLOCKWISE_COVERAGE_MASK) * |
| CLOCKWISE_COVERAGE_INVERSE_PRECISION; |
| half c1 = max(c0, fragCoverage); |
| X = incremental_clockwise_coverage(c0, c1, paintAlpha); |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| newCoverage = c1; |
| #endif |
| } |
| |
| paintAlpha *= X; |
| } |
| |
| INLINE void apply_fill_coverage(INOUT(float) paintAlpha, |
| half fragCoverageRemaining, |
| uint coverageIndex, |
| OUT(uint) preexistingCoverageValue, |
| OUT(half) newCoverage) |
| { |
| half X = .0; // Amount by which to multiply paintAlpha. |
| uint fragCoverageRemainingFixed = |
| uint(abs(fragCoverageRemaining) * CLOCKWISE_COVERAGE_PRECISION + .5); |
| |
| preexistingCoverageValue = |
| STORAGE_BUFFER_LOAD(coverageBuffer, coverageIndex); |
| |
| #ifdef @FIXED_FUNCTION_COLOR_OUTPUT |
| if (min(paintAlpha, fragCoverageRemaining) >= 1. && |
| (preexistingCoverageValue < uniforms.coverageBufferPrefix || |
| preexistingCoverageValue >= |
| (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 our color without working out coverage any |
| // further, even if another fragment from the path will get drawn on |
| // top. This is because any fragment drawn on top will be the same |
| // color, and any color blended onto a fully opaque version of itself is |
| // a no-op. |
| return; |
| } |
| #endif |
| |
| if (preexistingCoverageValue < 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); |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| preexistingCoverageValue = coverageBeforeMax; |
| #endif |
| 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 |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| newCoverage = X; |
| #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 c0Fixed = (coverageBeforeMax & CLOCKWISE_COVERAGE_MASK) - |
| CLOCKWISE_FILL_ZERO_VALUE; |
| half c0 = cast_uint_to_half(c0Fixed) * |
| CLOCKWISE_COVERAGE_INVERSE_PRECISION; |
| half c1 = fragCoverageRemaining; |
| #ifdef @DRAW_INTERIOR_TRIANGLES |
| c1 = min(c1, 1.); |
| #endif |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| newCoverage = c1; |
| #endif |
| // Apply the coverage increase from the atomicMax here. The next |
| // step will apply the remaining increase. |
| X = incremental_clockwise_coverage(c0, 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 = c0Fixed; |
| fragCoverageRemaining = c0; |
| } |
| } |
| |
| 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 c0 = |
| cast_int_to_half(int((coverageBeforeAdd & CLOCKWISE_COVERAGE_MASK) - |
| CLOCKWISE_FILL_ZERO_VALUE)) * |
| CLOCKWISE_COVERAGE_INVERSE_PRECISION; |
| half c1 = c0 + fragCoverageRemaining; |
| c0 = clamp(c0, .0, 1.); |
| c1 = clamp(c1, .0, 1.); |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| newCoverage = c1; |
| #endif |
| // Apply the coverage increase from c0 -> c1 that we just did, in |
| // addition to any coverage that had been applied previously. |
| X += (1. - X * paintAlpha) * |
| incremental_clockwise_coverage(c0, c1, paintAlpha); |
| } |
| |
| paintAlpha *= X; |
| } |
| |
| #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 |
| VARYING_UNPACK(v_coveragePlacement, uint2); |
| VARYING_UNPACK(v_coverageCoord, float2); |
| |
| half4 paintColor = find_paint_color(v_paint, 1. FRAGMENT_CONTEXT_UNPACK); |
| |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| // Fetch the framebuffer BEFORE any atomic operations on the coverage |
| // buffer. In order for advanced blend to work, we have to fetch the |
| // framebuffer value before checking if it's still valid. |
| half4 dstColor = PLS_LOAD4F(colorBuffer); |
| #endif |
| |
| half fragCoverage = |
| #ifdef @DRAW_INTERIOR_TRIANGLES |
| v_windingWeight; |
| #else |
| find_frag_coverage(v_coverages); |
| #endif |
| |
| float2 coverageCoord = v_coverageCoord; |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| // This little trick forces the shader to fetch the framebuffer BEFORE any |
| // atomic operations on the coverage buffer. (i.e., not to reorder the above |
| // fetch past this point). In order for advanced blend to work, we have to |
| // fetch the framebuffer value before operating on coverage. |
| // |
| // NOTE: Since v_coverageCoord is pixel-grid aligned, it will always have a |
| // fractional value of ~.5 (because varyings are sampled at at pixel |
| // center). So as long as colorBuffer is a standard unorm in the range 0..1, |
| // this will have literally no effect on the final outcome. If we ever |
| // support rendering to full floating point targets outside the range 0..1, |
| // we may need to put some more thought into this. |
| coverageCoord += |
| (dstColor.rg + dstColor.ba) * uniforms.epsilonForPseudoMemoryBarrier; |
| #endif |
| coverageCoord = floor(coverageCoord); |
| uint coveragePitch = v_coveragePlacement.y; |
| uint coverageIndex = |
| v_coveragePlacement.x + |
| swizzle_buffer_idx_32x32(uint2(coverageCoord), coveragePitch); |
| |
| #ifdef @ENABLE_CLIP_RECT |
| if (@ENABLE_CLIP_RECT) |
| { |
| half clipRectMin = min_value(cast_float4_to_half4(v_clipRect)); |
| fragCoverage = min(fragCoverage, clipRectMin); |
| } |
| #endif |
| |
| uint preexistingCoverageValue; |
| float newCoverage; |
| #ifndef @DRAW_INTERIOR_TRIANGLES |
| if (is_stroke(v_coverages)) |
| { |
| fragCoverage = clamp(fragCoverage, .0, 1.); |
| apply_stroke_coverage(paintColor.a, |
| fragCoverage, |
| coverageIndex, |
| preexistingCoverageValue, |
| newCoverage); |
| } |
| else // It's a fill. |
| #endif // !DRAW_INTERIOR_TRIANGLES |
| { |
| apply_fill_coverage(paintColor.a, |
| fragCoverage, |
| coverageIndex, |
| preexistingCoverageValue, |
| newCoverage); |
| } |
| |
| #ifdef @ENABLE_DITHER |
| half dither; |
| if (@ENABLE_DITHER) |
| { |
| dither = get_dither(_fragCoord.xy, |
| uniforms.ditherScale, |
| uniforms.ditherBias); |
| } |
| #endif |
| |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| if (paintColor.a > .0) |
| { |
| bool wasBlendColorValid = |
| preexistingCoverageValue >= uniforms.coverageBufferPrefix && |
| (preexistingCoverageValue & BLEND_COLOR_VALID_BIT) != 0u; |
| if (!wasBlendColorValid) |
| { |
| // If the saved blend color was not yet valid after we fetched |
| // dstColor, we are guaranteed that dstColor is valid because the |
| // BLEND_COLOR_VALID_BIT gets set before any color outputs that |
| // might overwrite the framebuffer. |
| // Calculate a blendColor based on dstColor. |
| paintColor.rgb = |
| advanced_color_blend(paintColor.rgb, |
| dstColor, |
| cast_half_to_ushort(v_blendMode)); |
| |
| // Anybody who updated, or will update, the coverage buffer before |
| // we overwrite the framebuffer is guaranteed to have a dstColor |
| // that is unaffected by our color output. They already have it. |
| // But if 0 < coverage < 1 after our fragment, we have to save out |
| // the blend color we just found for any future fragments that may |
| // need to blend, before we overwrite the contents of the |
| // framebuffer. |
| if (newCoverage < 1.) |
| { |
| half3 blendRGBToSave = paintColor.rgb; |
| #ifdef @ENABLE_DITHER |
| if (@ENABLE_DITHER) |
| { |
| blendRGBToSave += dither * uniforms.ditherConversionToRGB10; |
| } |
| #endif |
| PLS_STORE4F_ATOMIC(blendColorBuffer, |
| make_half4(blendRGBToSave, .0)); |
| |
| // Mark this pixel as having a valid blendColor, AFTER writing |
| // out the blendColor, but BEFORE updating the framebuffer. |
| memoryBarrier(); |
| STORAGE_BUFFER_ATOMIC_OR(coverageBuffer, |
| coverageIndex, |
| BLEND_COLOR_VALID_BIT); |
| } |
| } |
| else |
| { |
| // Use the saved blendColor whenever it's valid, because shortly |
| // after that point the framebuffer can be overwritten, invalidating |
| // the dstColor. |
| paintColor.rgb = PLS_LOAD4F_ATOMIC(blendColorBuffer).rgb; |
| } |
| } |
| #endif |
| |
| paintColor.rgb *= paintColor.a; |
| |
| #ifdef @ENABLE_DITHER |
| if (@ENABLE_DITHER) |
| { |
| paintColor.rgb += dither; |
| } |
| #endif |
| |
| // Since blend is enabled, storing 0 to the clip will ensure it remains |
| // unchanged. |
| PLS_STORE4F(clipBuffer, make_half4(.0)); |
| |
| #ifndef @FIXED_FUNCTION_COLOR_OUTPUT |
| PLS_STORE4F(colorBuffer, paintColor); |
| EMIT_PLS; |
| #else |
| _fragColor = paintColor; |
| EMIT_PLS_AND_FRAG_COLOR |
| #endif |
| } |
| |
| #endif // FRAGMENT |