blob: 8363b48c82a7189e87446cca255d90733e76ac71 [file] [log] [blame] [edit]
/*
* Copyright 2023 Rive
*/
#ifdef @DRAW_PATH
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
ATTR(0,
float4,
@a_patchVertexData); // [localVertexID, outset, fillCoverage, vertexType]
ATTR(1, float4, @a_mirroredVertexData);
ATTR_BLOCK_END
#endif
VARYING_BLOCK_BEGIN
#ifdef @ENABLE_FEATHER
NO_PERSPECTIVE VARYING(0, float4, v_coverages);
#else
NO_PERSPECTIVE VARYING(0, half2, v_coverages);
#endif
FLAT VARYING(1, ushort, v_pathID);
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);
#ifdef @ENABLE_FEATHER
VARYING_INIT(v_coverages, float4);
#else
VARYING_INIT(v_coverages, half2);
#endif
VARYING_INIT(v_pathID, ushort);
float4 pos;
uint pathID;
float2 vertexPosition;
float4 coverages;
if (unpack_tessellated_path_vertex(@a_patchVertexData,
@a_mirroredVertexData,
_instanceID,
pathID,
vertexPosition,
coverages VERTEX_CONTEXT_UNPACK))
{
#ifdef @ENABLE_FEATHER
v_coverages = coverages;
#else
v_coverages.xy = cast_float2_to_half2(coverages.xy);
#endif
v_pathID = cast_uint_to_ushort(pathID);
pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
}
else
{
pos = float4(uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue,
uniforms.vertexDiscardValue);
}
VARYING_PACK(v_coverages);
VARYING_PACK(v_pathID);
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
#ifdef @ATLAS_BLIT
NO_PERSPECTIVE VARYING(0, float2, v_atlasCoord);
#else
@OPTIONALLY_FLAT VARYING(0, half, v_windingWeight);
#endif
FLAT VARYING(1, ushort, v_pathID);
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
VARYING_INIT(v_windingWeight, half);
#endif
VARYING_INIT(v_pathID, ushort);
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);
#endif
v_pathID = cast_uint_to_ushort(pathID);
float4 pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
#ifdef @ATLAS_BLIT
VARYING_PACK(v_atlasCoord);
#else
VARYING_PACK(v_windingWeight);
#endif
VARYING_PACK(v_pathID);
EMIT_VERTEX(pos);
}
#endif // VERTEX
#endif // DRAW_INTERIOR_TRIANGLES
#ifdef @DRAW_IMAGE_RECT
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
ATTR(0, float4, @a_imageRectVertex);
ATTR_BLOCK_END
#endif
VARYING_BLOCK_BEGIN
NO_PERSPECTIVE VARYING(0, float2, v_texCoord);
NO_PERSPECTIVE VARYING(1, half, v_edgeCoverage);
#ifdef @ENABLE_CLIP_RECT
NO_PERSPECTIVE VARYING(2, float4, v_clipRect);
#endif
VARYING_BLOCK_END
#ifdef @VERTEX
IMAGE_RECT_VERTEX_MAIN(@drawVertexMain, Attrs, attrs, _vertexID, _instanceID)
{
ATTR_UNPACK(_vertexID, attrs, @a_imageRectVertex, float4);
VARYING_INIT(v_texCoord, float2);
VARYING_INIT(v_edgeCoverage, half);
#ifdef @ENABLE_CLIP_RECT
VARYING_INIT(v_clipRect, float4);
#endif
bool isOuterVertex =
@a_imageRectVertex.z == .0 || @a_imageRectVertex.w == .0;
v_edgeCoverage = isOuterVertex ? .0 : 1.;
float2 vertexPosition = @a_imageRectVertex.xy;
float2x2 M = make_float2x2(imageDrawUniforms.viewMatrix);
float2x2 MIT = transpose(inverse(M));
if (!isOuterVertex)
{
// Inset the inner vertices to the point where coverage == 1.
// NOTE: if width/height ever change from 1, these equations need to be
// updated.
float aaRadiusX =
AA_RADIUS * manhattan_width(MIT[1]) / dot(M[1], MIT[1]);
if (aaRadiusX >= .5)
{
vertexPosition.x = .5;
v_edgeCoverage *= cast_float_to_half(.5 / aaRadiusX);
}
else
{
vertexPosition.x += aaRadiusX * @a_imageRectVertex.z;
}
float aaRadiusY =
AA_RADIUS * manhattan_width(MIT[0]) / dot(M[0], MIT[0]);
if (aaRadiusY >= .5)
{
vertexPosition.y = .5;
v_edgeCoverage *= cast_float_to_half(.5 / aaRadiusY);
}
else
{
vertexPosition.y += aaRadiusY * @a_imageRectVertex.w;
}
}
v_texCoord = vertexPosition;
vertexPosition = MUL(M, vertexPosition) + imageDrawUniforms.translate;
if (isOuterVertex)
{
// Outset the outer vertices to the point where coverage == 0.
float2 n = MUL(MIT, @a_imageRectVertex.zw);
n *= manhattan_width(n) / dot(n, n);
vertexPosition += AA_RADIUS * n;
}
#ifdef @ENABLE_CLIP_RECT
if (@ENABLE_CLIP_RECT)
{
v_clipRect = find_clip_rect_coverage_distances(
make_float2x2(imageDrawUniforms.clipRectInverseMatrix),
imageDrawUniforms.clipRectInverseTranslate,
vertexPosition);
}
#endif
float4 pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
VARYING_PACK(v_texCoord);
VARYING_PACK(v_edgeCoverage);
#ifdef @ENABLE_CLIP_RECT
VARYING_PACK(v_clipRect);
#endif
EMIT_VERTEX(pos);
}
#endif // VERTEX
#elif defined(@DRAW_IMAGE_MESH)
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(PositionAttr)
ATTR(0, float2, @a_position);
ATTR_BLOCK_END
ATTR_BLOCK_BEGIN(UVAttr)
ATTR(1, float2, @a_texCoord);
ATTR_BLOCK_END
#endif
VARYING_BLOCK_BEGIN
NO_PERSPECTIVE VARYING(0, float2, v_texCoord);
#ifdef @ENABLE_CLIP_RECT
NO_PERSPECTIVE VARYING(1, float4, v_clipRect);
#endif
VARYING_BLOCK_END
#ifdef @VERTEX
IMAGE_MESH_VERTEX_MAIN(@drawVertexMain,
PositionAttr,
position,
UVAttr,
uv,
_vertexID)
{
ATTR_UNPACK(_vertexID, position, @a_position, float2);
ATTR_UNPACK(_vertexID, uv, @a_texCoord, float2);
VARYING_INIT(v_texCoord, float2);
#ifdef @ENABLE_CLIP_RECT
VARYING_INIT(v_clipRect, float4);
#endif
float2x2 M = make_float2x2(imageDrawUniforms.viewMatrix);
float2 vertexPosition = MUL(M, @a_position) + imageDrawUniforms.translate;
v_texCoord = @a_texCoord;
#ifdef @ENABLE_CLIP_RECT
if (@ENABLE_CLIP_RECT)
{
v_clipRect = find_clip_rect_coverage_distances(
make_float2x2(imageDrawUniforms.clipRectInverseMatrix),
imageDrawUniforms.clipRectInverseTranslate,
vertexPosition);
}
#endif
float4 pos = RENDER_TARGET_COORD_TO_CLIP_COORD(vertexPosition);
VARYING_PACK(v_texCoord);
#ifdef @ENABLE_CLIP_RECT
VARYING_PACK(v_clipRect);
#endif
EMIT_VERTEX(pos);
}
#endif // VERTEX
#endif // DRAW_IMAGE_MESH
#ifdef @DRAW_RENDER_TARGET_UPDATE_BOUNDS
#ifdef @VERTEX
ATTR_BLOCK_BEGIN(Attrs)
ATTR_BLOCK_END
#endif // VERTEX
VARYING_BLOCK_BEGIN
VARYING_BLOCK_END
#ifdef @VERTEX
VERTEX_MAIN(@drawVertexMain, Attrs, attrs, _vertexID, _instanceID)
{
int2 coord;
coord.x = (_vertexID & 1) == 0 ? uniforms.renderTargetUpdateBounds.x
: uniforms.renderTargetUpdateBounds.z;
coord.y = (_vertexID & 2) == 0 ? uniforms.renderTargetUpdateBounds.y
: uniforms.renderTargetUpdateBounds.w;
float4 pos = RENDER_TARGET_COORD_TO_CLIP_COORD(float2(coord));
EMIT_VERTEX(pos);
}
#endif // VERTEX
#endif // DRAW_RENDER_TARGET_UPDATE_BOUNDS
#ifdef @DRAW_IMAGE
#define NEEDS_IMAGE_TEXTURE
#endif
#ifdef @FRAGMENT
PLS_BLOCK_BEGIN
// We only bind the framebuffer as PLS when there are blend modes. Otherwise, we
// render to it as a normal color attachment.
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
#ifdef @COLOR_PLANE_IDX_OVERRIDE
// D3D11 doesn't let us bind the framebuffer UAV to slot 0 when there is a color
// output.
#define LOCAL_COLOR_PLANE_IDX @COLOR_PLANE_IDX_OVERRIDE
#else
#define LOCAL_COLOR_PLANE_IDX COLOR_PLANE_IDX
#endif
#ifdef @COALESCED_PLS_RESOLVE_AND_TRANSFER
PLS_DECL4F_READONLY(LOCAL_COLOR_PLANE_IDX, colorBuffer);
#else
PLS_DECL4F(LOCAL_COLOR_PLANE_IDX, colorBuffer);
#endif
#endif // !FIXED_FUNCTION_COLOR_OUTPUT
#ifdef @PLS_BLEND_SRC_OVER
// When PLS has src-over blending enabled, the clip buffer is RGBA8 so we can
// preserve clip contents by emitting a=0 instead of loading the current value.
// This is also is a hint to the hardware that it doesn't need to write anything
// to the clip attachment.
#define CLIP_VALUE_TYPE half4
#define PLS_LOAD_CLIP_TYPE PLS_LOAD4F
#define MAKE_NON_UPDATING_CLIP_VALUE make_half4(.0)
#define HAS_UPDATED_CLIP_VALUE(X) ((X).a != .0)
#ifdef @ENABLE_CLIPPING
#ifndef @RESOLVE_PLS
PLS_DECL4F(CLIP_PLANE_IDX, clipBuffer);
#else
PLS_DECL4F_READONLY(CLIP_PLANE_IDX, clipBuffer);
#endif
#endif // ENABLE_CLIPPING
#else
// When PLS does not have src-over blending, the clip buffer the usual packed
// R32UI.
#define CLIP_VALUE_TYPE uint
#define MAKE_NON_UPDATING_CLIP_VALUE 0u
#define PLS_LOAD_CLIP_TYPE PLS_LOADUI
#define HAS_UPDATED_CLIP_VALUE(X) ((X) != 0u)
#ifdef @ENABLE_CLIPPING
PLS_DECLUI(CLIP_PLANE_IDX, clipBuffer);
#endif // ENABLE_CLIPPING
#endif // !PLS_BLEND_SRC_OVER
PLS_DECLUI_ATOMIC(COVERAGE_PLANE_IDX, coverageAtomicBuffer);
PLS_BLOCK_END
FRAG_STORAGE_BUFFER_BLOCK_BEGIN
STORAGE_BUFFER_U32x2(PAINT_BUFFER_IDX, PaintBuffer, @paintBuffer);
STORAGE_BUFFER_F32x4(PAINT_AUX_BUFFER_IDX, PaintAuxBuffer, @paintAuxBuffer);
FRAG_STORAGE_BUFFER_BLOCK_END
INLINE uint to_fixed(float x)
{
return uint(round(x * FIXED_COVERAGE_PRECISION + FIXED_COVERAGE_ZERO));
}
INLINE half from_fixed(uint x)
{
return cast_float_to_half(
float(x) * FIXED_COVERAGE_INVERSE_PRECISION +
(-FIXED_COVERAGE_ZERO * FIXED_COVERAGE_INVERSE_PRECISION));
}
#ifdef @ENABLE_CLIPPING
INLINE void apply_clip(uint clipID,
CLIP_VALUE_TYPE clipData,
INOUT(half) coverage)
{
#ifdef @PLS_BLEND_SRC_OVER
// The clipID is packed into r & g of clipData. Use a fuzzy equality test
// since we lose precision when the clip value gets stored to and from the
// attachment.
if (all(lessThan(abs(clipData.rg - unpackUnorm4x8(clipID).rg),
make_half2(.25 / 255.))))
coverage = min(coverage, clipData.b);
else
coverage = .0;
#else
// The clipID is the top 16 bits of clipData.
if (clipID == clipData >> 16)
coverage = min(coverage, unpackHalf2x16(clipData).r);
else
coverage = .0;
#endif
}
#endif
INLINE void resolve_paint(uint pathID,
half coverageCount,
OUT(half4) fragColorOut
#if defined(@ENABLE_CLIPPING) && !defined(@RESOLVE_PLS)
,
INOUT(CLIP_VALUE_TYPE) fragClipOut
#endif
FRAGMENT_CONTEXT_DECL PLS_CONTEXT_DECL)
{
uint2 paintData = STORAGE_BUFFER_LOAD2(@paintBuffer, pathID);
half coverage = coverageCount;
if ((paintData.x & (PAINT_FLAG_NON_ZERO_FILL | PAINT_FLAG_EVEN_ODD_FILL)) !=
0u)
{
// This path has a legacy (non-clockwise) fill.
coverage = abs(coverage);
#ifdef @ENABLE_EVEN_ODD
if (@ENABLE_EVEN_ODD && (paintData.x & PAINT_FLAG_EVEN_ODD_FILL) != 0u)
{
coverage = 1. - abs(fract(coverage * .5) * 2. + -1.);
}
#endif
}
// This also caps stroke coverage, which can be >1.
coverage = clamp(coverage, make_half(.0), make_half(1.));
#ifdef @ENABLE_CLIPPING
if (@ENABLE_CLIPPING)
{
uint clipID = paintData.x >> 16u;
if (clipID != 0u)
{
apply_clip(clipID, PLS_LOAD_CLIP_TYPE(clipBuffer), coverage);
}
}
#endif // ENABLE_CLIPPING
#ifdef @ENABLE_CLIP_RECT
if (@ENABLE_CLIP_RECT && (paintData.x & PAINT_FLAG_HAS_CLIP_RECT) != 0u)
{
float2x2 M = make_float2x2(
STORAGE_BUFFER_LOAD4(@paintAuxBuffer, pathID * 4u + 2u));
float4 translate =
STORAGE_BUFFER_LOAD4(@paintAuxBuffer, pathID * 4u + 3u);
float2 clipCoord = MUL(M, _fragCoord) + translate.xy;
// translate.zw contains -1 / fwidth(clipCoord), which we use to
// calculate antialiasing.
half2 distXY =
cast_float2_to_half2(abs(clipCoord) * translate.zw - translate.zw);
half clipRectCoverage = clamp(min(distXY.x, distXY.y) + .5, .0, 1.);
coverage = min(coverage, clipRectCoverage);
}
#endif // ENABLE_CLIP_RECT
uint paintType = paintData.x & 0xfu;
if (paintType <= SOLID_COLOR_PAINT_TYPE) // CLIP_UPDATE_PAINT_TYPE or
// SOLID_COLOR_PAINT_TYPE
{
fragColorOut = unpackUnorm4x8(paintData.y);
#ifdef @ENABLE_CLIPPING
if (@ENABLE_CLIPPING && paintType == CLIP_UPDATE_PAINT_TYPE)
{
#ifndef @RESOLVE_PLS
#ifdef @PLS_BLEND_SRC_OVER
// This was actually a clip update. fragColorOut contains the packed
// clipID.
fragClipOut.rg = fragColorOut.ba; // Pack the clipID into r & g.
fragClipOut.b = coverage; // Put the clipCoverage in b.
fragClipOut.a =
1.; // a=1 so we replace the value in the clipBuffer.
#else
fragClipOut = paintData.y | packHalf2x16(make_half2(coverage, .0));
#endif
#endif
// Don't update the colorBuffer when this is a clip update.
fragColorOut = make_half4(.0);
}
#endif
}
else // LINEAR_GRADIENT_PAINT_TYPE or RADIAL_GRADIENT_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;
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);
fragColorOut =
TEXTURE_SAMPLE_LOD(@gradTexture, gradSampler, float2(x, y), .0);
}
fragColorOut.a *= coverage;
#if !defined(@FIXED_FUNCTION_COLOR_OUTPUT) && defined(@ENABLE_ADVANCED_BLEND)
// Apply the advanced blend mode, if applicable.
ushort blendMode;
if (@ENABLE_ADVANCED_BLEND && fragColorOut.a != .0 &&
(blendMode = cast_uint_to_ushort((paintData.x >> 4) & 0xfu)) != 0u)
{
half4 dstColorPremul = PLS_LOAD4F(colorBuffer);
fragColorOut.rgb =
advanced_color_blend(fragColorOut.rgb, dstColorPremul, blendMode);
}
#endif // !FIXED_FUNCTION_COLOR_OUTPUT && ENABLE_ADVANCED_BLEND
// When PLS_BLEND_SRC_OVER is defined, the caller and/or blend state
// multiply alpha into fragColorOut for us. Otherwise, we have to
// premultiply it.
#ifndef @PLS_BLEND_SRC_OVER
fragColorOut.rgb *= fragColorOut.a;
#endif
// Certain platforms give us less control of the format of what we are
// rendering too. Specifically, we are auto converted from linear -> sRGB on
// render target writes in unreal. In those cases we made need to end up in
// linear color space
#ifdef @NEEDS_GAMMA_CORRECTION
fragColorOut = gamma_to_linear(fragColorOut);
#endif
}
#if !defined(@FIXED_FUNCTION_COLOR_OUTPUT) && \
!defined(@COALESCED_PLS_RESOLVE_AND_TRANSFER)
INLINE void blend_pls_color_src_over(half4 fragColorOut PLS_CONTEXT_DECL)
{
#ifndef @PLS_BLEND_SRC_OVER
if (fragColorOut.a == .0)
return;
float oneMinusSrcAlpha = 1. - fragColorOut.a;
if (oneMinusSrcAlpha != .0)
fragColorOut += PLS_LOAD4F(colorBuffer) * oneMinusSrcAlpha;
#endif
PLS_STORE4F(colorBuffer, fragColorOut);
}
#endif // !@FIXED_FUNCTION_COLOR_OUTPUT && !@COALESCED_PLS_RESOLVE_AND_TRANSFER
#if defined(@ENABLE_CLIPPING) && !defined(@RESOLVE_PLS)
INLINE void emit_pls_clip(CLIP_VALUE_TYPE fragClipOut PLS_CONTEXT_DECL)
{
#ifdef @PLS_BLEND_SRC_OVER
PLS_STORE4F(clipBuffer, fragClipOut);
#else
if (fragClipOut != 0u)
PLS_STOREUI(clipBuffer, fragClipOut);
#endif
}
#endif // ENABLE_CLIPPING && !RESOLVE_PLS
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
#define ATOMIC_PLS_MAIN PLS_FRAG_COLOR_MAIN
#define ATOMIC_PLS_MAIN_WITH_IMAGE_UNIFORMS \
PLS_FRAG_COLOR_MAIN_WITH_IMAGE_UNIFORMS
#define EMIT_ATOMIC_PLS EMIT_PLS_AND_FRAG_COLOR
#else // !FIXED_FUNCTION_COLOR_OUTPUT
#define ATOMIC_PLS_MAIN PLS_MAIN
#define ATOMIC_PLS_MAIN_WITH_IMAGE_UNIFORMS PLS_MAIN_WITH_IMAGE_UNIFORMS
#define EMIT_ATOMIC_PLS EMIT_PLS
#endif
#ifdef @DRAW_PATH
ATOMIC_PLS_MAIN(@drawFragmentMain)
{
#ifdef @ENABLE_FEATHER
VARYING_UNPACK(v_coverages, float4);
#else
VARYING_UNPACK(v_coverages, half2);
#endif
VARYING_UNPACK(v_pathID, ushort);
half fragmentCoverage;
#ifdef @ENABLE_FEATHER
if (@ENABLE_FEATHER && is_feathered_stroke(v_coverages))
{
fragmentCoverage =
eval_feathered_stroke(v_coverages TEXTURE_CONTEXT_FORWARD);
}
else if (@ENABLE_FEATHER && is_feathered_fill(v_coverages))
{
fragmentCoverage =
eval_feathered_fill(v_coverages TEXTURE_CONTEXT_FORWARD);
}
else
#endif
{
// Cover stroke and fill both in a branchless expression.
fragmentCoverage =
min(min(make_half(v_coverages.x), abs(make_half(v_coverages.y))),
make_half(1.));
}
half4 fragColorOut = make_half4(.0);
#ifdef @ENABLE_CLIPPING
CLIP_VALUE_TYPE fragClipOut = MAKE_NON_UPDATING_CLIP_VALUE;
#endif
// Since v_pathID increases monotonically with every draw, and since it
// lives in the most significant bits of the coverage data, an atomic max()
// function will serve 3 purposes:
//
// * The invocation that changes the pathID is the single first fragment
// invocation to hit the new path, and the one that should resolve the
// previous path in the framebuffer.
// * Properly resets coverage to zero when we do cross over into
// processing a new path.
// * Accumulates coverage for strokes.
//
uint fixedCoverage = to_fixed(fragmentCoverage);
uint minCoverageData =
(make_uint(v_pathID) << FIXED_COVERAGE_BIT_COUNT) | fixedCoverage;
uint lastCoverageData =
PLS_ATOMIC_MAX(coverageAtomicBuffer, minCoverageData);
ushort lastPathID =
cast_uint_to_ushort(lastCoverageData >> FIXED_COVERAGE_BIT_COUNT);
if (lastPathID == v_pathID)
{
// This is not the first fragment of the current path to touch this
// pixel. We already resolved the previous path, so just update coverage
// (if we're a fill) and move on.
if (!is_stroke(v_coverages))
{
// Only apply the effect of the min() the first time we cross into a
// path.
fixedCoverage +=
lastCoverageData - max(minCoverageData, lastCoverageData);
fixedCoverage -=
FIXED_COVERAGE_ZERO_UINT; // Only apply the zero bias once.
PLS_ATOMIC_ADD(coverageAtomicBuffer,
fixedCoverage); // Count coverage.
}
}
else
{
// We crossed into a new path! Resolve the previous path now that we
// know its exact coverage.
half coverageCount = from_fixed(lastCoverageData & FIXED_COVERAGE_MASK);
resolve_paint(lastPathID,
coverageCount,
fragColorOut
#ifdef @ENABLE_CLIPPING
,
fragClipOut
#endif
FRAGMENT_CONTEXT_UNPACK PLS_CONTEXT_UNPACK);
}
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
_fragColor = fragColorOut;
#else
blend_pls_color_src_over(fragColorOut PLS_CONTEXT_UNPACK);
#endif
#ifdef @ENABLE_CLIPPING
emit_pls_clip(fragClipOut PLS_CONTEXT_UNPACK);
#endif
EMIT_ATOMIC_PLS
}
#endif // DRAW_PATH
#ifdef @DRAW_INTERIOR_TRIANGLES
ATOMIC_PLS_MAIN(@drawFragmentMain)
{
#ifdef @ATLAS_BLIT
VARYING_UNPACK(v_atlasCoord, float2);
#else
VARYING_UNPACK(v_windingWeight, half);
#endif
VARYING_UNPACK(v_pathID, ushort);
uint lastCoverageData = PLS_LOADUI_ATOMIC(coverageAtomicBuffer);
ushort lastPathID =
cast_uint_to_ushort(lastCoverageData >> FIXED_COVERAGE_BIT_COUNT);
// Update coverageAtomicBuffer with the coverage weight of the current
// triangle. This does not need to be atomic since interior triangles don't
// overlap.
uint currPathCoverageData;
#ifndef @ATLAS_BLIT
if (lastPathID == v_pathID)
{
currPathCoverageData = lastCoverageData;
}
else
#endif
{
currPathCoverageData =
(make_uint(v_pathID) << FIXED_COVERAGE_BIT_COUNT) +
FIXED_COVERAGE_ZERO_UINT;
}
half coverage;
#ifdef @ATLAS_BLIT
coverage = filter_feather_atlas(
v_atlasCoord,
uniforms.atlasTextureInverseSize TEXTURE_CONTEXT_FORWARD);
#else
coverage = v_windingWeight;
#endif
int coverageDeltaFixed = int(round(coverage * FIXED_COVERAGE_PRECISION));
PLS_STOREUI_ATOMIC(coverageAtomicBuffer,
currPathCoverageData + uint(coverageDeltaFixed));
half4 fragColorOut = make_half4(.0);
#ifdef @ENABLE_CLIPPING
CLIP_VALUE_TYPE fragClipOut = MAKE_NON_UPDATING_CLIP_VALUE;
#endif
#ifndef @ATLAS_BLIT
// If this is not the first fragment of the current path to touch this
// pixel, then we've already resolved the previous path and can move on.
if (lastPathID != v_pathID)
#endif
{
// We crossed into a new path! Resolve the previous path now that we
// know its exact coverage.
half lastCoverageCount =
from_fixed(lastCoverageData & FIXED_COVERAGE_MASK);
resolve_paint(lastPathID,
lastCoverageCount,
fragColorOut
#ifdef @ENABLE_CLIPPING
,
fragClipOut
#endif
FRAGMENT_CONTEXT_UNPACK PLS_CONTEXT_UNPACK);
}
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
_fragColor = fragColorOut;
#else
blend_pls_color_src_over(fragColorOut PLS_CONTEXT_UNPACK);
#endif
#ifdef @ENABLE_CLIPPING
emit_pls_clip(fragClipOut PLS_CONTEXT_UNPACK);
#endif
EMIT_ATOMIC_PLS
}
#endif // DRAW_INTERIOR_TRIANGLES
#ifdef @DRAW_IMAGE
ATOMIC_PLS_MAIN_WITH_IMAGE_UNIFORMS(@drawFragmentMain)
{
VARYING_UNPACK(v_texCoord, float2);
#ifdef @DRAW_IMAGE_RECT
VARYING_UNPACK(v_edgeCoverage, half);
#endif
#ifdef @ENABLE_CLIP_RECT
VARYING_UNPACK(v_clipRect, float4);
#endif
// Start by finding the image color. We have to do this immediately instead
// of allowing it to get resolved later like other draws because the
// @imageTexture binding is liable to change, and furthermore in the case of
// imageMeshes, we can't calculate UV coordinates based on fragment
// position.
half4 imageColor =
TEXTURE_SAMPLE_DYNAMIC(@imageTexture, imageSampler, v_texCoord);
half imageCoverage = 1.;
#ifdef @DRAW_IMAGE_RECT
imageCoverage = min(v_edgeCoverage, imageCoverage);
#endif
#ifdef @ENABLE_CLIP_RECT
if (@ENABLE_CLIP_RECT)
{
half clipRectCoverage = min_value(cast_float4_to_half4(v_clipRect));
imageCoverage = clamp(clipRectCoverage, make_half(.0), imageCoverage);
}
#endif
// Resolve the previous path.
uint lastCoverageData = PLS_LOADUI_ATOMIC(coverageAtomicBuffer);
ushort lastPathID =
cast_uint_to_ushort(lastCoverageData >> FIXED_COVERAGE_BIT_COUNT);
half lastCoverageCount = from_fixed(lastCoverageData & FIXED_COVERAGE_MASK);
half4 fragColorOut;
#ifdef @ENABLE_CLIPPING
CLIP_VALUE_TYPE fragClipOut = MAKE_NON_UPDATING_CLIP_VALUE;
#endif
// TODO: consider not resolving the prior paint if we're solid and the prior
// paint is not a clip update: (imageColor.a == 1. &&
// imageDrawUniforms.blendMode ==
// BLEND_SRC_OVER && priorPaintType !=
// CLIP_UPDATE_PAINT_TYPE)
resolve_paint(lastPathID,
lastCoverageCount,
fragColorOut
#ifdef @ENABLE_CLIPPING
,
fragClipOut
#endif
FRAGMENT_CONTEXT_UNPACK PLS_CONTEXT_UNPACK);
#ifdef @PLS_BLEND_SRC_OVER
// Image draws use a premultiplied blend state, but resolve_paint() did not
// premultiply fragColorOut. Multiply fragColorOut by alpha now.
fragColorOut.rgb *= fragColorOut.a;
#endif
// Clip the image after resolving the previous path, since that can affect
// the clip buffer.
#ifdef @ENABLE_CLIPPING // TODO! ENABLE_IMAGE_CLIPPING in addition to
// ENABLE_CLIPPING?
if (@ENABLE_CLIPPING && imageDrawUniforms.clipID != 0u)
{
CLIP_VALUE_TYPE clipData = HAS_UPDATED_CLIP_VALUE(fragClipOut)
? fragClipOut
: PLS_LOAD_CLIP_TYPE(clipBuffer);
apply_clip(imageDrawUniforms.clipID, clipData, imageCoverage);
}
#endif // ENABLE_CLIPPING
// Prepare imageColor for premultiplied src-over blending.
#if !defined(@FIXED_FUNCTION_COLOR_OUTPUT) && defined(@ENABLE_ADVANCED_BLEND)
if (@ENABLE_ADVANCED_BLEND && imageDrawUniforms.blendMode != BLEND_SRC_OVER)
{
// Calculate what dstColorPremul will be after applying fragColorOut.
half4 dstColorPremul =
PLS_LOAD4F(colorBuffer) * (1. - fragColorOut.a) + fragColorOut;
// Calculate the imageColor to emit *BEFORE* src-over blending, such
// that the post-src-over-blend result is equivalent to the blendMode.
imageColor.rgb = advanced_color_blend(
unmultiply_rgb(imageColor),
dstColorPremul,
cast_uint_to_ushort(imageDrawUniforms.blendMode)) *
imageColor.a;
}
#endif // !FIXED_FUNCTION_COLOR_OUTPUT && ENABLE_ADVANCED_BLEND
imageColor *= imageCoverage * cast_float_to_half(imageDrawUniforms.opacity);
// Leverage the property that premultiplied src-over blending is associative
// and blend the imageColor and fragColorOut before passing them on to the
// blending pipeline.
fragColorOut = fragColorOut * (1. - imageColor.a) + imageColor;
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
_fragColor = fragColorOut;
#else
blend_pls_color_src_over(fragColorOut PLS_CONTEXT_UNPACK);
#endif
#ifdef @ENABLE_CLIPPING
emit_pls_clip(fragClipOut PLS_CONTEXT_UNPACK);
#endif
// Write out a coverage value of "zero at pathID=0" so a future resolve
// attempt doesn't affect this pixel.
PLS_STOREUI_ATOMIC(coverageAtomicBuffer, FIXED_COVERAGE_ZERO_UINT);
EMIT_ATOMIC_PLS
}
#endif // DRAW_IMAGE
#ifdef @INITIALIZE_PLS
ATOMIC_PLS_MAIN(@drawFragmentMain)
{
#ifdef @STORE_COLOR_CLEAR
PLS_STORE4F(colorBuffer, unpackUnorm4x8(uniforms.colorClearValue));
#endif
#ifdef @SWIZZLE_COLOR_BGRA_TO_RGBA
half4 color = PLS_LOAD4F(colorBuffer);
PLS_STORE4F(colorBuffer, color.bgra);
#endif
PLS_STOREUI_ATOMIC(coverageAtomicBuffer, uniforms.coverageClearValue);
#ifdef @ENABLE_CLIPPING
if (@ENABLE_CLIPPING)
{
PLS_STOREUI(clipBuffer, 0u);
}
#endif
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
discard;
#endif
EMIT_ATOMIC_PLS
}
#endif // INITIALIZE_PLS
#ifdef @RESOLVE_PLS
#ifdef @COALESCED_PLS_RESOLVE_AND_TRANSFER
PLS_FRAG_COLOR_MAIN(@drawFragmentMain)
#else
ATOMIC_PLS_MAIN(@drawFragmentMain)
#endif
{
uint lastCoverageData = PLS_LOADUI_ATOMIC(coverageAtomicBuffer);
half coverageCount = from_fixed(lastCoverageData & FIXED_COVERAGE_MASK);
ushort lastPathID =
cast_uint_to_ushort(lastCoverageData >> FIXED_COVERAGE_BIT_COUNT);
half4 fragColorOut;
resolve_paint(lastPathID,
coverageCount,
fragColorOut FRAGMENT_CONTEXT_UNPACK PLS_CONTEXT_UNPACK);
#ifdef @COALESCED_PLS_RESOLVE_AND_TRANSFER
#ifdef @PLS_BLEND_SRC_OVER
// When PLS_BLEND_SRC_OVER is defined, the blend state usually multiplies
// alpha into fragColorOut for us. But since the coalesced resolve does not
// use blend, premultiply it now.
fragColorOut.rgb *= fragColorOut.a;
#endif
float oneMinusSrcAlpha = 1. - fragColorOut.a;
if (oneMinusSrcAlpha != .0)
fragColorOut += PLS_LOAD4F(colorBuffer) * oneMinusSrcAlpha;
_fragColor = fragColorOut;
EMIT_PLS_AND_FRAG_COLOR
#else
#ifdef @FIXED_FUNCTION_COLOR_OUTPUT
_fragColor = fragColorOut;
#else
blend_pls_color_src_over(fragColorOut PLS_CONTEXT_UNPACK);
#endif
EMIT_ATOMIC_PLS
#endif // COALESCED_PLS_RESOLVE_AND_TRANSFER
}
#endif // RESOLVE_PLS
#endif // FRAGMENT