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