blob: 62d0d80e959a25df5eb36b393051de7fd87f4617 [file] [log] [blame]
/*
* Copyright 2023 Rive
*/
#ifdef @DRAW_PATH
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
// [localVertexID, outset, fillCoverage, vertexType]
ATTR(0, float4, @a_patchVertexData);
ATTR(1, float4, @a_mirroredVertexData);
ATTR_BLOCK_END
#endif
VARYING_BLOCK_BEGIN
FLAT VARYING(0, ushort, v_pathID);
NO_PERSPECTIVE VARYING(1, float4, v_coverages);
NO_PERSPECTIVE VARYING(2, float2, v_atlasCoord);
FLAT VARYING(3, uint2, v_coveragePlacement);
VARYING(4, float2, v_coverageCoord);
VARYING_BLOCK_END
#ifdef @VERTEX
VERTEX_MAIN(@drawVertexMain, Attrs, attrs, _vertexID, _instanceID)
{
ATTR_UNPACK(_vertexID, attrs, @a_patchVertexData, float4);
ATTR_UNPACK(_vertexID, attrs, @a_mirroredVertexData, float4);
VARYING_INIT(v_pathID, ushort);
VARYING_INIT(v_coverages, float4);
VARYING_INIT(v_coveragePlacement, uint2);
VARYING_INIT(v_coverageCoord, float2);
float4 pos;
uint pathID;
float2 vertexPosition;
if (unpack_tessellated_path_vertex(@a_patchVertexData,
@a_mirroredVertexData,
_instanceID,
pathID,
vertexPosition,
v_coverages VERTEX_CONTEXT_UNPACK))
{
uint4 coverageData =
STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u + 3u);
v_pathID = pathID;
v_coveragePlacement = coverageData.xy;
v_coverageCoord = vertexPosition + uintBitsToFloat(coverageData.zw);
pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
}
else
{
pos = float4(uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue);
}
VARYING_PACK(v_pathID);
VARYING_PACK(v_coverages);
VARYING_PACK(v_coveragePlacement);
VARYING_PACK(v_coverageCoord);
EMIT_VERTEX(pos);
}
#endif // VERTEX
#endif // DRAW_PATH
#ifdef @DRAW_INTERIOR_TRIANGLES
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
ATTR(0, packed_float3, @a_triangleVertex);
ATTR_BLOCK_END
#endif
VARYING_BLOCK_BEGIN
FLAT VARYING(0, ushort, v_pathID);
#ifdef @ATLAS_BLIT
NO_PERSPECTIVE VARYING(1, float2, v_atlasCoord);
#else
@OPTIONALLY_FLAT VARYING(1, half, v_windingWeight);
FLAT VARYING(2, uint2, v_coveragePlacement);
VARYING(3, float2, v_coverageCoord);
#endif
VARYING_BLOCK_END
#ifdef @VERTEX
VERTEX_MAIN(@drawVertexMain, Attrs, attrs, _vertexID, _instanceID)
{
ATTR_UNPACK(_vertexID, attrs, @a_triangleVertex, float3);
#ifdef @ATLAS_BLIT
VARYING_INIT(v_atlasCoord, float2);
#else
#endif
VARYING_INIT(v_pathID, ushort);
#ifdef @ATLAS_BLIT
VARYING_INIT(v_atlasCoord, float2);
#else
VARYING_INIT(v_windingWeight, half);
VARYING_INIT(v_coveragePlacement, uint2);
VARYING_INIT(v_coverageCoord, float2);
#endif
uint pathID;
float2 vertexPosition;
#ifdef @ATLAS_BLIT
vertexPosition =
unpack_atlas_coverage_vertex(@a_triangleVertex,
pathID,
v_atlasCoord VERTEX_CONTEXT_UNPACK);
#else
vertexPosition =
unpack_interior_triangle_vertex(@a_triangleVertex,
pathID,
v_windingWeight VERTEX_CONTEXT_UNPACK);
uint4 coverageData = STORAGE_BUFFER_LOAD4(@pathBuffer, pathID * 4u + 3u);
v_coveragePlacement = coverageData.xy;
v_coverageCoord = vertexPosition + uintBitsToFloat(coverageData.zw);
#endif
v_pathID = cast_uint_to_ushort(pathID);
float4 pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
VARYING_PACK(v_pathID);
#ifdef @ATLAS_BLIT
VARYING_PACK(v_atlasCoord);
#else
VARYING_PACK(v_windingWeight);
VARYING_PACK(v_coveragePlacement);
VARYING_PACK(v_coverageCoord);
#endif
EMIT_VERTEX(pos);
}
#endif // VERTEX
#endif // DRAW_INTERIOR_TRIANGLES
#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_PREPASS
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_pathID, ushort);
#ifdef DRAW_PATH
VARYING_UNPACK(v_coverages, float4);
#elif defined(@ATLAS_BLIT)
VARYING_UNPACK(v_atlasCoord, float2);
#else
VARYING_UNPACK(v_windingWeight, half);
#endif
#ifndef @ATLAS_BLIT
VARYING_UNPACK(v_coveragePlacement, uint2);
VARYING_UNPACK(v_coverageCoord, float2);
#endif
half4 paintColor;
uint pathID = v_pathID;
uint2 paintData = STORAGE_BUFFER_LOAD2(@paintBuffer, pathID);
uint paintType = paintData.x & 0xfu;
if (paintType <= SOLID_COLOR_PAINT_TYPE) // CLIP_UPDATE_PAINT_TYPE or
// SOLID_COLOR_PAINT_TYPE
{
paintColor = unpackUnorm4x8(paintData.y);
}
else // LINEAR_GRADIENT_PAINT_TYPE,
// RADIAL_GRADIENT_PAINT_TYPE, or
// IMAGE_PAINT_TYPE
{
float2x2 M =
make_float2x2(STORAGE_BUFFER_LOAD4(@paintAuxBuffer, pathID * 4u));
float4 translate =
STORAGE_BUFFER_LOAD4(@paintAuxBuffer, pathID * 4u + 1u);
float2 paintCoord = MUL(M, _fragCoord) + translate.xy;
if (paintType != IMAGE_PAINT_TYPE)
{
float t = paintType == LINEAR_GRADIENT_PAINT_TYPE
? /*linear*/ paintCoord.x
: /*radial*/ length(paintCoord);
t = clamp(t, .0, 1.);
float x = t * translate.z + translate.w;
float y = uintBitsToFloat(paintData.y);
paintColor =
TEXTURE_SAMPLE_LOD(@gradTexture, gradSampler, float2(x, y), .0);
}
else
{
float opacity = uintBitsToFloat(paintData.y);
float lod = translate.z;
paintColor = TEXTURE_SAMPLE_LOD(@imageTexture,
imageSampler,
paintCoord,
lod);
paintColor =
make_half4(unmultiply_rgb(paintColor), paintColor.a * opacity);
}
}
if (paintColor.a == .0)
{
discard;
}
#ifdef @ATLAS_BLIT
paintColor.a *= filter_feather_atlas(
v_atlasCoord,
uniforms.atlasTextureInverseSize TEXTURE_CONTEXT_FORWARD);
#else
// 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_PREPASS
if (@BORROWED_COVERAGE_PREPASS)
{
#ifdef @DRAW_INTERIOR_TRIANGLES
half borrowedCoverage = -v_windingWeight;
#else
half fragCoverage;
#ifdef @ENABLE_FEATHER
if (@ENABLE_FEATHER && is_feathered_fill(v_coverages))
{
fragCoverage =
eval_feathered_fill(v_coverages TEXTURE_CONTEXT_FORWARD);
}
else
#endif
{
fragCoverage = v_coverages.x;
}
half borrowedCoverage = max(-fragCoverage, .0);
#endif
apply_borrowed_coverage(borrowedCoverage, coverageIndex);
discard;
}
#endif // BORROWED_COVERAGE_PREPASS
#ifndef @DRAW_INTERIOR_TRIANGLES
if (is_stroke(v_coverages))
{
half fragCoverage;
#ifdef @ENABLE_FEATHER
if (@ENABLE_FEATHER && is_feathered_stroke(v_coverages))
{
fragCoverage =
eval_feathered_stroke(v_coverages TEXTURE_CONTEXT_FORWARD);
}
else
#endif
{
fragCoverage = min(v_coverages.x, v_coverages.y);
}
fragCoverage = clamp(fragCoverage, .0, 1.);
apply_stroke_coverage(paintColor.a, fragCoverage, coverageIndex);
}
else // It's a fill.
#endif // !DRAW_INTERIOR_TRIANGLES
{
#ifdef @DRAW_INTERIOR_TRIANGLES
half fragCoverage = v_windingWeight;
#else
half fragCoverage;
#ifdef @ENABLE_FEATHER
if (@ENABLE_FEATHER && is_feathered_fill(v_coverages))
{
fragCoverage =
eval_feathered_fill(v_coverages TEXTURE_CONTEXT_FORWARD);
}
else
#endif
{
fragCoverage = v_coverages.x;
}
fragCoverage = clamp(fragCoverage, .0, 1.);
#endif
apply_fill_coverage(paintColor.a, fragCoverage, coverageIndex);
}
#endif // @ATLAS_BLIT
EMIT_FRAG_DATA(paintColor);
}
#endif // FRAGMENT